Skip to content

Commit

Permalink
Add Duck Player prime modal
Browse files Browse the repository at this point in the history
  • Loading branch information
CrisBarreiro committed Aug 20, 2024
1 parent 2d3b27f commit 864511c
Show file tree
Hide file tree
Showing 14 changed files with 357 additions and 8 deletions.
61 changes: 53 additions & 8 deletions app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ import android.annotation.SuppressLint
import android.app.Activity.RESULT_OK
import android.app.ActivityOptions
import android.app.PendingIntent
import android.content.*
import android.content.ActivityNotFoundException
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
Expand All @@ -31,7 +35,12 @@ import android.graphics.Color
import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable
import android.net.Uri
import android.os.*
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.print.PrintAttributes
import android.print.PrintManager
import android.provider.MediaStore
Expand All @@ -40,8 +49,14 @@ import android.text.Spannable
import android.text.SpannableString
import android.text.Spanned
import android.text.style.StyleSpan
import android.view.*
import android.view.View.*
import android.view.ContextMenu
import android.view.KeyEvent
import android.view.MenuItem
import android.view.View
import android.view.View.GONE
import android.view.View.OnFocusChangeListener
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams
import android.view.inputmethod.EditorInfo
import android.webkit.PermissionRequest
Expand All @@ -53,7 +68,9 @@ import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebView.FindListener
import android.webkit.WebView.HitTestResult
import android.webkit.WebView.HitTestResult.*
import android.webkit.WebView.HitTestResult.IMAGE_TYPE
import android.webkit.WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
import android.webkit.WebView.HitTestResult.UNKNOWN_TYPE
import android.widget.EditText
import android.widget.FrameLayout
import android.widget.TextView
Expand All @@ -68,13 +85,21 @@ import androidx.core.net.toUri
import androidx.core.text.HtmlCompat
import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY
import androidx.core.text.toSpannable
import androidx.core.view.*
import androidx.core.view.doOnLayout
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.core.view.postDelayed
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.commitNow
import androidx.fragment.app.setFragmentResultListener
import androidx.fragment.app.transaction
import androidx.lifecycle.*
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.webkit.JavaScriptReplyProxy
import androidx.webkit.WebMessageCompat
Expand Down Expand Up @@ -157,6 +182,11 @@ import com.duckduckgo.app.browser.webview.WebContentDebugging
import com.duckduckgo.app.browser.webview.WebViewBlobDownloadFeature
import com.duckduckgo.app.browser.webview.safewebview.SafeWebViewFeature
import com.duckduckgo.app.cta.ui.*
import com.duckduckgo.app.cta.ui.Cta
import com.duckduckgo.app.cta.ui.CtaViewModel
import com.duckduckgo.app.cta.ui.DaxBubbleCta
import com.duckduckgo.app.cta.ui.HomePanelCta
import com.duckduckgo.app.cta.ui.OnboardingDaxDialogCta
import com.duckduckgo.app.di.AppCoroutineScope
import com.duckduckgo.app.fire.fireproofwebsite.data.FireproofWebsiteEntity
import com.duckduckgo.app.fire.fireproofwebsite.data.website
Expand Down Expand Up @@ -250,6 +280,7 @@ import com.duckduckgo.downloads.api.DownloadConfirmationDialogListener
import com.duckduckgo.downloads.api.DownloadsFileActions
import com.duckduckgo.downloads.api.FileDownloader
import com.duckduckgo.downloads.api.FileDownloader.PendingFileDownload
import com.duckduckgo.duckplayer.api.DuckPlayer
import com.duckduckgo.duckplayer.api.DuckPlayerSettingsNoParams
import com.duckduckgo.js.messaging.api.JsCallbackData
import com.duckduckgo.js.messaging.api.JsMessageCallback
Expand Down Expand Up @@ -286,10 +317,16 @@ import javax.inject.Inject
import javax.inject.Named
import javax.inject.Provider
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.Runnable
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.cancellable
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONObject
import timber.log.Timber

Expand Down Expand Up @@ -499,6 +536,9 @@ class BrowserTabFragment :
@Inject
lateinit var safeWebViewFeature: SafeWebViewFeature

@Inject
lateinit var duckPlayer: DuckPlayer

/**
* We use this to monitor whether the user was seeing the in-context Email Protection signup prompt
* This is needed because the activity stack will be cleared if an external link is opened in our browser
Expand Down Expand Up @@ -1545,6 +1585,11 @@ class BrowserTabFragment :
is Command.LaunchScreen -> launchScreen(it.screen, it.payload)
is Command.HideOnboardingDaxDialog -> hideOnboardingDaxDialog(it.onboardingCta)
is Command.OpenDuckPlayerSettings -> globalActivityStarter.start(binding.root.context, DuckPlayerSettingsNoParams)
is Command.OpenDuckPlayerInfo -> {
context?.resources?.configuration?.let {
duckPlayer.showDuckPlayerPrimeModal(it, childFragmentManager)
}
}
else -> {
// NO OP
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.duckduckgo.app.browser.duckplayer

import com.duckduckgo.app.browser.commands.Command
import com.duckduckgo.app.browser.commands.Command.OpenDuckPlayerInfo
import com.duckduckgo.app.browser.commands.Command.OpenDuckPlayerSettings
import com.duckduckgo.app.browser.commands.Command.SendResponseToDuckPlayer
import com.duckduckgo.app.browser.commands.Command.SendResponseToJs
Expand Down Expand Up @@ -168,6 +169,9 @@ class DuckPlayerJSHelper @Inject constructor(
"openSettings" -> {
return OpenDuckPlayerSettings
}
"openInfo" -> {
return OpenDuckPlayerInfo
}
else -> {
return null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ class ContentScopeScriptsJsMessaging @Inject constructor(
"sendDuckPlayerPixel",
"setUserValues",
"openDuckPlayer",
"openInfo",
"initialSetup",
"reportPageException",
"reportInitException",
Expand Down
1 change: 1 addition & 0 deletions duckplayer/duckplayer-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ dependencies {
implementation Kotlin.stdlib.jdk7
implementation AndroidX.core.ktx
implementation KotlinX.coroutines.core
implementation AndroidX.fragment.ktx
coreLibraryDesugaring Android.tools.desugarJdkLibs
implementation project(path: ':common-utils')
implementation project(':navigation-api')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@

package com.duckduckgo.duckplayer.api

import android.content.res.Configuration
import android.net.Uri
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebView
import androidx.fragment.app.FragmentManager
import com.duckduckgo.duckplayer.api.PrivatePlayerMode.AlwaysAsk
import com.duckduckgo.duckplayer.api.PrivatePlayerMode.Disabled
import com.duckduckgo.duckplayer.api.PrivatePlayerMode.Enabled
Expand Down Expand Up @@ -143,6 +145,14 @@ interface DuckPlayer {
webView: WebView,
): WebResourceResponse?

/**
* Shows the Duck Player Prime modal.
*
* @param configuration The configuration of the device.
* @param fragmentManager The fragment manager.
*/
fun showDuckPlayerPrimeModal(configuration: Configuration, fragmentManager: FragmentManager)

/**
* Data class representing user preferences for Duck Player.
*
Expand Down
1 change: 1 addition & 0 deletions duckplayer/duckplayer-impl/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ dependencies {
implementation Google.android.material
implementation Google.dagger

implementation "com.airbnb.android:lottie:_"
implementation "com.squareup.logcat:logcat:_"

testImplementation Testing.junit4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@

package com.duckduckgo.duckplayer.impl

import android.content.res.Configuration
import android.net.Uri
import android.webkit.MimeTypeMap
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebView
import androidx.core.net.toUri
import androidx.fragment.app.FragmentManager
import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.common.utils.DispatcherProvider
import com.duckduckgo.common.utils.UrlScheme.Companion.duck
Expand All @@ -32,6 +34,8 @@ import com.duckduckgo.duckplayer.api.DuckPlayer.UserPreferences
import com.duckduckgo.duckplayer.api.PrivatePlayerMode.AlwaysAsk
import com.duckduckgo.duckplayer.api.PrivatePlayerMode.Disabled
import com.duckduckgo.duckplayer.api.PrivatePlayerMode.Enabled
import com.duckduckgo.duckplayer.impl.ui.DuckPlayerPrimeBottomSheet
import com.duckduckgo.duckplayer.impl.ui.DuckPlayerPrimeDialogFragment
import com.squareup.anvil.annotations.ContributesBinding
import dagger.SingleInstanceIn
import java.io.InputStream
Expand Down Expand Up @@ -270,4 +274,12 @@ class RealDuckPlayer @Inject constructor(
}
return WebResourceResponse(null, null, null)
}

override fun showDuckPlayerPrimeModal(configuration: Configuration, fragmentManager: FragmentManager) {
if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
DuckPlayerPrimeDialogFragment.newInstance().show(fragmentManager, null)
} else {
DuckPlayerPrimeBottomSheet.newInstance().show(fragmentManager, null)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright (c) 2024 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.duckplayer.impl.ui

import android.app.Dialog
import android.content.res.Configuration
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.airbnb.lottie.LottieCompositionFactory
import com.airbnb.lottie.LottieDrawable
import com.duckduckgo.duckplayer.impl.R
import com.duckduckgo.duckplayer.impl.databinding.ModalDuckPlayerBinding
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment

class DuckPlayerPrimeBottomSheet : BottomSheetDialogFragment() {

private lateinit var binding: ModalDuckPlayerBinding

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
binding = ModalDuckPlayerBinding.inflate(inflater, container, false)
LottieCompositionFactory.fromRawRes(context, R.raw.duckplayer)
binding.duckPlayerAnimation.setAnimation(R.raw.duckplayer)
binding.duckPlayerAnimation.playAnimation()
binding.duckPlayerAnimation.repeatCount = LottieDrawable.INFINITE
binding.dismissButton.setOnClickListener {
dismiss()
}
binding.closeButton.setOnClickListener {
dismiss()
}
return binding.root
}

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog
dialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED
return dialog
}

override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
dismiss()
}

companion object {
fun newInstance(): DuckPlayerPrimeBottomSheet =
DuckPlayerPrimeBottomSheet()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright (c) 2024 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.duckplayer.impl.ui

import android.content.res.Configuration
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.fragment.app.DialogFragment
import com.airbnb.lottie.LottieCompositionFactory
import com.airbnb.lottie.LottieDrawable
import com.duckduckgo.duckplayer.impl.R
import com.duckduckgo.duckplayer.impl.databinding.ModalDuckPlayerBinding

class DuckPlayerPrimeDialogFragment : DialogFragment() {

private lateinit var binding: ModalDuckPlayerBinding

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
binding = ModalDuckPlayerBinding.inflate(inflater, container, false)
LottieCompositionFactory.fromRawRes(context, R.raw.duckplayer)
binding.duckPlayerAnimation.setAnimation(R.raw.duckplayer)
binding.duckPlayerAnimation.playAnimation()
binding.duckPlayerAnimation.repeatCount = LottieDrawable.INFINITE
binding.dismissButton.setOnClickListener {
dismiss()
}
binding.closeButton.setOnClickListener {
dismiss()
}
return binding.root
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, com.duckduckgo.mobile.android.R.style.Widget_DuckDuckGo_DialogFullScreen)
}
override fun onStart() {
super.onStart()
dialog?.window?.let {
WindowCompat.getInsetsController(it, binding.root).apply {
systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
hide(WindowInsetsCompat.Type.statusBars())
}
}
}

override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
dismiss()
}
companion object {
fun newInstance(): DuckPlayerPrimeDialogFragment =
DuckPlayerPrimeDialogFragment()
}
}
Loading

0 comments on commit 864511c

Please sign in to comment.