16
16
17
17
package com.duckduckgo.duckchat.impl.inputscreen.ui
18
18
19
+ import android.animation.ValueAnimator
19
20
import android.app.Activity
20
21
import android.content.Intent
21
22
import android.os.Bundle
@@ -25,10 +26,12 @@ import androidx.core.view.isInvisible
25
26
import androidx.core.view.isVisible
26
27
import androidx.lifecycle.ViewModelProvider
27
28
import androidx.lifecycle.lifecycleScope
29
+ import androidx.viewpager2.widget.ViewPager2
28
30
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
29
31
import com.duckduckgo.anvil.annotations.InjectWith
30
32
import com.duckduckgo.app.statistics.pixels.Pixel
31
33
import com.duckduckgo.common.ui.DuckDuckGoFragment
34
+ import com.duckduckgo.common.ui.store.AppTheme
32
35
import com.duckduckgo.common.ui.viewbinding.viewBinding
33
36
import com.duckduckgo.common.utils.extensions.hideKeyboard
34
37
import com.duckduckgo.common.utils.extensions.showKeyboard
@@ -39,8 +42,11 @@ import com.duckduckgo.duckchat.api.inputscreen.InputScreenActivityResultParams
39
42
import com.duckduckgo.duckchat.impl.R
40
43
import com.duckduckgo.duckchat.impl.databinding.FragmentInputScreenBinding
41
44
import com.duckduckgo.duckchat.impl.inputscreen.ui.command.Command
45
+ import com.duckduckgo.duckchat.impl.inputscreen.ui.command.Command.AnimateLogoToProgress
42
46
import com.duckduckgo.duckchat.impl.inputscreen.ui.command.Command.EditWithSelectedQuery
43
47
import com.duckduckgo.duckchat.impl.inputscreen.ui.command.Command.HideKeyboard
48
+ import com.duckduckgo.duckchat.impl.inputscreen.ui.command.Command.SetInputModeWidgetScrollPosition
49
+ import com.duckduckgo.duckchat.impl.inputscreen.ui.command.Command.SetLogoProgress
44
50
import com.duckduckgo.duckchat.impl.inputscreen.ui.command.Command.ShowKeyboard
45
51
import com.duckduckgo.duckchat.impl.inputscreen.ui.command.Command.SubmitChat
46
52
import com.duckduckgo.duckchat.impl.inputscreen.ui.command.Command.SubmitSearch
@@ -80,6 +86,9 @@ class InputScreenFragment : DuckDuckGoFragment(R.layout.fragment_input_screen) {
80
86
@Inject
81
87
lateinit var viewModelFactory: InputScreenViewModelFactory
82
88
89
+ @Inject
90
+ lateinit var appTheme: AppTheme
91
+
83
92
private val viewModel: InputScreenViewModel by lazy {
84
93
val params = requireActivity().intent.getActivityParams(InputScreenActivityParams ::class .java)
85
94
val currentOmnibarText = params?.query ? : " "
@@ -91,11 +100,23 @@ class InputScreenFragment : DuckDuckGoFragment(R.layout.fragment_input_screen) {
91
100
92
101
private val pageChangeCallback = object : OnPageChangeCallback () {
93
102
override fun onPageSelected (position : Int ) {
103
+ viewModel.onPageSelected(position)
94
104
binding.inputModeWidget.selectTab(position)
95
105
}
106
+
107
+ override fun onPageScrolled (position : Int , positionOffset : Float , positionOffsetPixels : Int ) {
108
+ viewModel.onPageScrolled(position, positionOffset)
109
+ }
110
+
111
+ override fun onPageScrollStateChanged (state : Int ) {
112
+ if (state == ViewPager2 .SCROLL_STATE_IDLE ) {
113
+ viewModel.onScrollStateIdle()
114
+ }
115
+ }
96
116
}
97
117
98
118
private lateinit var pagerAdapter: InputScreenPagerAdapter
119
+ private var logoAnimator: ValueAnimator ? = null
99
120
100
121
override fun onViewCreated (view : View , savedInstanceState : Bundle ? ) {
101
122
super .onViewCreated(view, savedInstanceState)
@@ -109,6 +130,7 @@ class InputScreenFragment : DuckDuckGoFragment(R.layout.fragment_input_screen) {
109
130
configureOmnibar()
110
131
configureVoice()
111
132
configureObservers()
133
+ configureLogoAnimation()
112
134
113
135
binding.inputModeWidget.init ()
114
136
@@ -168,11 +190,15 @@ class InputScreenFragment : DuckDuckGoFragment(R.layout.fragment_input_screen) {
168
190
}.launchIn(lifecycleScope)
169
191
170
192
viewModel.visibilityState.onEach {
171
- binding.ddgLogo.isVisible = if (binding.viewPager.currentItem == 0 ) {
193
+ val isSearchMode = binding.viewPager.currentItem == 0
194
+ binding.ddgLogoContainer.isVisible = if (isSearchMode) {
172
195
it.showSearchLogo
173
196
} else {
174
197
it.showChatLogo
175
198
}
199
+
200
+ binding.ddgLogo.progress = if (isSearchMode) 0f else 1f
201
+
176
202
binding.actionNewLine.isVisible = it.newLineButtonVisible
177
203
}.launchIn(lifecycleScope)
178
204
}
@@ -190,6 +216,9 @@ class InputScreenFragment : DuckDuckGoFragment(R.layout.fragment_input_screen) {
190
216
is SubmitChat -> submitChatQuery(command.query)
191
217
is ShowKeyboard -> showKeyboard(binding.inputModeWidget.inputField)
192
218
is HideKeyboard -> hideKeyboard(binding.inputModeWidget.inputField)
219
+ is SetInputModeWidgetScrollPosition -> binding.inputModeWidget.setScrollPosition(command.position, command.offset)
220
+ is SetLogoProgress -> setLogoProgress(command.targetProgress)
221
+ is AnimateLogoToProgress -> animateLogoToProgress(command.targetProgress)
193
222
}
194
223
}
195
224
@@ -213,23 +242,19 @@ class InputScreenFragment : DuckDuckGoFragment(R.layout.fragment_input_screen) {
213
242
binding.viewPager.setCurrentItem(0 , true )
214
243
viewModel.onSearchSelected()
215
244
viewModel.onSearchInputTextChanged(binding.inputModeWidget.text)
216
- binding.ddgLogo.apply {
217
- setImageResource(com.duckduckgo.mobile.android.R .drawable.logo_full)
218
- isVisible = viewModel.visibilityState.value.showSearchLogo
219
- }
245
+ binding.ddgLogoContainer.isVisible = viewModel.visibilityState.value.showSearchLogo
220
246
}
221
247
onChatSelected = {
222
248
binding.viewPager.setCurrentItem(1 , true )
223
249
viewModel.onChatSelected()
224
250
viewModel.onChatInputTextChanged(binding.inputModeWidget.text)
225
- binding.ddgLogo.apply {
226
- setImageResource(R .drawable.logo_full_ai)
251
+ binding.ddgLogoContainer.apply {
227
252
val showChatLogo = viewModel.visibilityState.value.showChatLogo
228
253
val showSearchLogo = viewModel.visibilityState.value.showSearchLogo
229
254
isVisible = showChatLogo
230
255
if (showChatLogo && ! showSearchLogo) {
231
256
alpha = 0f
232
- animate().alpha(1f ).setDuration(200L ).start()
257
+ animate().alpha(1f ).setDuration(LOGO_FADE_DURATION ).start()
233
258
}
234
259
}
235
260
}
@@ -248,6 +273,9 @@ class InputScreenFragment : DuckDuckGoFragment(R.layout.fragment_input_screen) {
248
273
onInputFieldClicked = {
249
274
viewModel.onInputFieldTouched()
250
275
}
276
+ onTabTapped = { index ->
277
+ viewModel.onTabTapped(index)
278
+ }
251
279
}
252
280
253
281
private fun submitChatQuery (query : String ) {
@@ -282,12 +310,41 @@ class InputScreenFragment : DuckDuckGoFragment(R.layout.fragment_input_screen) {
282
310
}.launchIn(lifecycleScope)
283
311
}
284
312
313
+ private fun configureLogoAnimation () = with (binding.ddgLogo) {
314
+ setMinAndMaxFrame(0 , LOGO_MAX_FRAME )
315
+ setAnimation(
316
+ if (appTheme.isLightModeEnabled()) {
317
+ R .raw.duckduckgo_ai_transition_light
318
+ } else {
319
+ R .raw.duckduckgo_ai_transition_dark
320
+ },
321
+ )
322
+ }
323
+
324
+ private fun setLogoProgress (targetProgress : Float ) {
325
+ binding.ddgLogo.progress = targetProgress
326
+ }
327
+
328
+ private fun animateLogoToProgress (targetProgress : Float ) {
329
+ logoAnimator?.cancel()
330
+ binding.ddgLogo.apply {
331
+ logoAnimator = ValueAnimator .ofFloat(progress, targetProgress).apply {
332
+ duration = LOGO_ANIMATION_DURATION
333
+ addUpdateListener { progress = it.animatedValue as Float }
334
+ start()
335
+ }
336
+ }
337
+ }
338
+
285
339
private fun exitInputScreen () {
286
340
hideKeyboard(binding.inputModeWidget.inputField)
287
341
requireActivity().supportFinishAfterTransition()
288
342
}
289
343
290
344
override fun onDestroyView () {
345
+ logoAnimator?.cancel()
346
+ logoAnimator = null
347
+ binding.ddgLogo.clearAnimation()
291
348
binding.viewPager.unregisterOnPageChangeCallback(pageChangeCallback)
292
349
super .onDestroyView()
293
350
}
@@ -296,4 +353,10 @@ class InputScreenFragment : DuckDuckGoFragment(R.layout.fragment_input_screen) {
296
353
super .onResume()
297
354
viewModel.onActivityResume()
298
355
}
356
+
357
+ companion object {
358
+ const val LOGO_ANIMATION_DURATION = 350L
359
+ const val LOGO_MAX_FRAME = 15
360
+ const val LOGO_FADE_DURATION = 200L
361
+ }
299
362
}
0 commit comments