diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreMainActivity.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreMainActivity.kt index 3577aa62c..5d9e709c7 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreMainActivity.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreMainActivity.kt @@ -287,7 +287,7 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider { drawerContainerLayout.closeDrawer(drawerNavView) } - private fun openSupportKiwixExternalLink() { + fun openSupportKiwixExternalLink() { externalLinkOpener.openExternalUrl(KIWIX_SUPPORT_URL.toUri().browserIntent()) } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt index e95d01394..ce195456a 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt @@ -46,6 +46,7 @@ import android.view.MenuInflater import android.view.MenuItem import android.view.MotionEvent import android.view.View +import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup import android.view.animation.AnimationUtils @@ -133,6 +134,8 @@ import org.kiwix.kiwixmobile.core.reader.ZimReaderSource import org.kiwix.kiwixmobile.core.search.viewmodel.effects.SearchItemToOpen import org.kiwix.kiwixmobile.core.utils.AnimationUtils.rotate import org.kiwix.kiwixmobile.core.utils.DimenUtils.getToolbarHeight +import org.kiwix.kiwixmobile.core.utils.DonationDialogHandler +import org.kiwix.kiwixmobile.core.utils.DonationDialogHandler.ShowDonationDialogCallback import org.kiwix.kiwixmobile.core.utils.ExternalLinkOpener import org.kiwix.kiwixmobile.core.utils.LanguageUtils import org.kiwix.kiwixmobile.core.utils.LanguageUtils.Companion.getCurrentLocale @@ -173,7 +176,8 @@ abstract class CoreReaderFragment : FragmentActivityExtensions, WebViewProvider, ReadAloudCallbacks, - NavigationHistoryClickListener { + NavigationHistoryClickListener, + ShowDonationDialogCallback { protected val webViewList: MutableList = ArrayList() private val webUrlsProcessor = BehaviorProcessor.create() private var fragmentReaderBinding: FragmentReaderBinding? = null @@ -227,6 +231,10 @@ abstract class CoreReaderFragment : @Inject var alertDialogShower: DialogShower? = null + @JvmField + @Inject + var donationDialogHandler: DonationDialogHandler? = null + @JvmField @Inject var painter: DarkModeViewPainter? = null @@ -297,6 +305,7 @@ abstract class CoreReaderFragment : private var tableDrawerAdapter: TableDrawerAdapter? = null private var tableDrawerRight: RecyclerView? = null private var tabCallback: ItemTouchHelper.Callback? = null + private var donationLayout: FrameLayout? = null private var bookmarkingDisposable: Disposable? = null private var isBookmarked = false private lateinit var serviceConnection: ServiceConnection @@ -389,6 +398,7 @@ abstract class CoreReaderFragment : ) { super.onViewCreated(view, savedInstanceState) setupMenu() + donationDialogHandler?.showDonationDialogCallBack(this) val activity = requireActivity() as AppCompatActivity? activity?.let { WebView(it).destroy() // Workaround for buggy webViews see #710 @@ -511,6 +521,7 @@ abstract class CoreReaderFragment : tabRecyclerView = findViewById(R.id.tab_switcher_recycler_view) snackBarRoot = findViewById(R.id.snackbar_root) bottomToolbarToc = findViewById(R.id.bottom_toolbar_toc) + donationLayout = findViewById(R.id.donation_layout) } } } @@ -1213,6 +1224,8 @@ abstract class CoreReaderFragment : unRegisterReadAloudService() storagePermissionForNotesLauncher?.unregister() storagePermissionForNotesLauncher = null + donationDialogHandler?.showDonationDialogCallBack(null) + donationDialogHandler = null } private fun unBindViewsAndBinding() { @@ -1243,6 +1256,8 @@ abstract class CoreReaderFragment : closeAllTabsButton = null tableDrawerRightContainer = null fragmentReaderBinding = null + donationLayout?.removeAllViews() + donationLayout = null } private fun updateTableOfContents() { @@ -1846,6 +1861,53 @@ abstract class CoreReaderFragment : if (tts == null) { setUpTTS() } + donationDialogHandler?.attemptToShowDonationPopup() + } + + @Suppress("InflateParams", "MagicNumber") + protected open fun showDonationLayout() { + val donationCardView = layoutInflater.inflate(R.layout.layout_donation_bottom_sheet, null) + val layoutParams = FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.WRAP_CONTENT + ) + val bottomToolBarHeight = + requireActivity() + .findViewById(org.kiwix.kiwixmobile.core.R.id.bottom_toolbar).measuredHeight + layoutParams.setMargins(16, 0, 16, bottomToolBarHeight + 10) + + donationCardView.layoutParams = layoutParams + donationLayout?.apply { + removeAllViews() + addView(donationCardView) + setDonationLayoutVisibility(VISIBLE) + } + donationCardView.findViewById(R.id.descriptionText).apply { + text = getString( + R.string.donation_dialog_description, + (requireActivity() as CoreMainActivity).appName + ) + } + val donateButton: TextView = donationCardView.findViewById(R.id.donateButton) + donateButton.setOnClickListener { + donationDialogHandler?.updateLastDonationPopupShownTime() + setDonationLayoutVisibility(GONE) + openKiwixSupportUrl() + } + + val laterButton: TextView = donationCardView.findViewById(R.id.laterButton) + laterButton.setOnClickListener { + donationDialogHandler?.donateLater() + setDonationLayoutVisibility(GONE) + } + } + + protected open fun openKiwixSupportUrl() { + (requireActivity() as CoreMainActivity).openSupportKiwixExternalLink() + } + + private fun setDonationLayoutVisibility(visibility: Int) { + donationLayout?.visibility = visibility } private fun openFullScreenIfEnabled() { @@ -2388,6 +2450,10 @@ abstract class CoreReaderFragment : unbindService() } + override fun showDonationDialog() { + showDonationLayout() + } + private fun bindService() { requireActivity().bindService( Intent(requireActivity(), ReadAloudService::class.java), serviceConnection, diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/DonationDialogHandler.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/DonationDialogHandler.kt new file mode 100644 index 000000000..669c6811b --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/DonationDialogHandler.kt @@ -0,0 +1,82 @@ +/* + * Kiwix Android + * Copyright (c) 2024 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 android.app.Activity +import org.kiwix.kiwixmobile.core.dao.NewBookDao +import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.isCustomApp +import javax.inject.Inject + +const val THREE_DAYS_IN_MILLISECONDS = 3 * 24 * 60 * 60 * 1000L +const val THREE_MONTHS_IN_MILLISECONDS = 90 * 24 * 60 * 60 * 1000L + +class DonationDialogHandler @Inject constructor( + private val activity: Activity, + private val sharedPreferenceUtil: SharedPreferenceUtil, + private val newBookDao: NewBookDao +) { + + private var showDonationDialogCallback: ShowDonationDialogCallback? = null + + fun showDonationDialogCallBack(showDonationDialogCallback: ShowDonationDialogCallback?) { + this.showDonationDialogCallback = showDonationDialogCallback + } + + fun attemptToShowDonationPopup() { + val currentMilliSeconds = System.currentTimeMillis() + val lastPopupMillis = sharedPreferenceUtil.lastDonationPopupShownInMilliSeconds + val timeDifference = currentMilliSeconds - lastPopupMillis + if (shouldShowInitialPopup(lastPopupMillis) || timeDifference >= THREE_MONTHS_IN_MILLISECONDS) { + if (isZimFilesAvailableInLibrary() && isTimeToShowDonation(currentMilliSeconds)) { + showDonationDialogCallback?.showDonationDialog() + resetDonateLater() + } + } + } + + private fun shouldShowInitialPopup(lastPopupMillis: Long): Boolean = lastPopupMillis == 0L + private fun isTimeToShowDonation(currentMillis: Long): Boolean = + isLaterNotClicked() || isLaterPeriodOver(currentMillis) + + private fun isLaterNotClicked(): Boolean = sharedPreferenceUtil.laterClickedMilliSeconds == 0L + + private fun isLaterPeriodOver(currentMillis: Long): Boolean { + val timeDifference = currentMillis - sharedPreferenceUtil.laterClickedMilliSeconds + return timeDifference >= THREE_DAYS_IN_MILLISECONDS + } + + private fun isZimFilesAvailableInLibrary(): Boolean = + if (activity.isCustomApp()) true else newBookDao.getBooks().isNotEmpty() + + fun updateLastDonationPopupShownTime() { + sharedPreferenceUtil.lastDonationPopupShownInMilliSeconds = System.currentTimeMillis() + } + + fun donateLater() { + sharedPreferenceUtil.laterClickedMilliSeconds = System.currentTimeMillis() + } + + private fun resetDonateLater() { + sharedPreferenceUtil.laterClickedMilliSeconds = 0L + } + + interface ShowDonationDialogCallback { + fun showDonationDialog() + } +} 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 323facf7a..e0af5b648 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 @@ -271,6 +271,22 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) { } } + var lastDonationPopupShownInMilliSeconds: Long + get() = sharedPreferences.getLong(PREF_LAST_DONATION_POPUP_SHOWN_IN_MILLISECONDS, 0L) + set(value) { + sharedPreferences.edit { + putLong(PREF_LAST_DONATION_POPUP_SHOWN_IN_MILLISECONDS, value) + } + } + + var laterClickedMilliSeconds: Long + get() = sharedPreferences.getLong(PREF_LATER_CLICKED_MILLIS, 0L) + set(value) { + sharedPreferences.edit { + putLong(PREF_LATER_CLICKED_MILLIS, value) + } + } + fun getPublicDirectoryPath(path: String): String = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { path @@ -321,5 +337,8 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) { const val PREF_NOTES_MIGRATED = "pref_notes_migrated" const val PREF_APP_DIRECTORY_TO_PUBLIC_MIGRATED = "pref_app_directory_to_public_migrated" const val PREF_COPY_MOVE_PERMISSION = "pref_copy_move_permission" + private const val PREF_LATER_CLICKED_MILLIS = "pref_later_clicked_millis" + private const val PREF_LAST_DONATION_POPUP_SHOWN_IN_MILLISECONDS = + "pref_last_donation_shown_in_milliseconds" } } diff --git a/core/src/main/res/layout/fragment_reader.xml b/core/src/main/res/layout/fragment_reader.xml index 703248209..2432de5c0 100644 --- a/core/src/main/res/layout/fragment_reader.xml +++ b/core/src/main/res/layout/fragment_reader.xml @@ -57,6 +57,14 @@ android:layout_height="match_parent" android:background="@android:color/black" android:visibility="invisible" /> + + diff --git a/core/src/main/res/layout/layout_donation_bottom_sheet.xml b/core/src/main/res/layout/layout_donation_bottom_sheet.xml new file mode 100644 index 000000000..618609ec6 --- /dev/null +++ b/core/src/main/res/layout/layout_donation_bottom_sheet.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index ea6803dc6..16283d303 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -389,4 +389,7 @@ Go to previous screen Save or Open this file? Choosing Open will open this file in external reader application. + Donate Today + %s needs your help. + Make a donation diff --git a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt index 1926899bf..2ccc70b1f 100644 --- a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt +++ b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt @@ -28,10 +28,12 @@ import android.view.View.VISIBLE import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar +import androidx.core.net.toUri import androidx.drawerlayout.widget.DrawerLayout import androidx.navigation.fragment.findNavController import org.kiwix.kiwixmobile.core.R.dimen import org.kiwix.kiwixmobile.core.base.BaseActivity +import org.kiwix.kiwixmobile.core.extensions.browserIntent import org.kiwix.kiwixmobile.core.extensions.getResizedDrawable import org.kiwix.kiwixmobile.core.extensions.isFileExist import org.kiwix.kiwixmobile.core.main.CoreReaderFragment @@ -328,6 +330,22 @@ class CustomReaderFragment : CoreReaderFragment() { newMainPageTab() } + /** + * Overrides the method to show the donation popup. When the "Support url" is disabled + * in a custom app, this function stop to show the donationPopup. + */ + override fun showDonationLayout() { + if (BuildConfig.SUPPORT_URL.isNotEmpty()) { + super.showDonationLayout() + } + } + + override fun openKiwixSupportUrl() { + if (BuildConfig.SUPPORT_URL.isNotEmpty()) { + openExternalUrl(BuildConfig.SUPPORT_URL.toUri().browserIntent()) + } + } + override fun onDestroyView() { super.onDestroyView() permissionRequiredDialog = null