Browse Source

New widget explanation when a widget is added for the first time

merge-requests/1492/head
Nicolas Pomepuy 4 years ago
committed by Duncan McNamara
parent
commit
96fbe84638
  1. BIN
      application/resources/src/main/res/drawable-nodpi/vlc_widget_macro.png
  2. BIN
      application/resources/src/main/res/drawable-nodpi/vlc_widget_micro.png
  3. BIN
      application/resources/src/main/res/drawable-nodpi/vlc_widget_mini.png
  4. BIN
      application/resources/src/main/res/drawable-nodpi/vlc_widget_pill.png
  5. 4
      application/resources/src/main/res/values/strings.xml
  6. 1
      application/tools/src/main/java/org/videolan/tools/Settings.kt
  7. 200
      application/vlc-android/res/layout/dialog_widget_explanation.xml
  8. 6
      application/vlc-android/res/menu/widget_configure_option.xml
  9. 183
      application/vlc-android/src/org/videolan/vlc/gui/dialogs/WidgetExplanationDialog.kt
  10. 79
      application/vlc-android/src/org/videolan/vlc/gui/view/WidgetHandleView.kt
  11. 22
      application/vlc-android/src/org/videolan/vlc/widget/MiniPlayerConfigureActivity.kt

BIN
application/resources/src/main/res/drawable-nodpi/vlc_widget_macro.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

BIN
application/resources/src/main/res/drawable-nodpi/vlc_widget_micro.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
application/resources/src/main/res/drawable-nodpi/vlc_widget_mini.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

BIN
application/resources/src/main/res/drawable-nodpi/vlc_widget_pill.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

4
application/resources/src/main/res/values/strings.xml

@ -566,6 +566,10 @@
<string name="permission_onboarding_perm_all">You will be able to auto-discover all your files to play all types of media even weird ones, or compressed files</string>
<string name="exit_app">Close VLC</string>
<string name="exit_app_msg">Are you sure you want to quit VLC?</string>
<string name="widget_explanation_title">VLC widgets</string>
<string name="widget_explanation_size">The widgets come in different sizes and flavors</string>
<string name="widget_explanation_resize">To change the widget type, long press it and use the handles to resize it to the size you like</string>
<string name="widget_explanation_end">Widgets are heavily customizable. Unleash your creativity and make them look like you!</string>
<!-- fast scroller -->
<string name="fastscroller_track">FastScroller track</string>

1
application/tools/src/main/java/org/videolan/tools/Settings.kt

@ -104,6 +104,7 @@ const val ML_SCAN_OFF = 1
//Tips
const val PREF_TIPS_SHOWN = "video_player_tips_shown"
const val PREF_WIDGETS_TIPS_SHOWN = "widgets_tips_shown"
const val PREF_TV_UI = "tv_ui"
const val FORCE_PLAY_ALL = "force_play_all"

200
application/vlc-android/res/layout/dialog_widget_explanation.xml

@ -0,0 +1,200 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ *************************************************************************
~ dialog_widget_explanation.xml
~ **************************************************************************
~ Copyright © 2022 VLC authors and VideoLAN
~ Author: Nicolas POMEPUY
~ 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 2 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, write to the Free Software
~ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
~ ***************************************************************************
~
~
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
</data>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.Group
android:id="@+id/step1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="widget_sizes,textView32"
tools:visibility="visible" />
<androidx.constraintlayout.widget.Group
android:id="@+id/step2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="widget_resize,widget_resize_handle,resizeLongTapIcon,widget_resize_text" />
<androidx.constraintlayout.widget.Group
android:id="@+id/step3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="widget_explanation_step3_text,imageView19" />
<TextView
android:id="@+id/title"
style="@style/Theme.VLC.BottomSheetTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:text="@string/widget_explanation_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView32"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:fontFamily="sans-serif-medium"
android:text="@string/widget_explanation_size"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
<ImageView
android:id="@+id/widget_sizes"
android:layout_width="0dp"
android:layout_height="256dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:padding="16dp"
android:scaleType="centerInside"
app:layout_constraintBottom_toTopOf="@+id/widget_next_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView32"
app:srcCompat="@drawable/widget_preview_mini" />
<TextView
android:id="@+id/widget_resize_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:fontFamily="sans-serif-medium"
android:text="@string/widget_explanation_resize"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
<ImageView
android:id="@+id/widget_resize"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:padding="8dp"
android:src="@drawable/vlc_widget_mini"
app:layout_constraintBottom_toTopOf="@+id/widget_next_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/widget_resize_text"
app:srcCompat="@drawable/widget_preview_mini" />
<org.videolan.vlc.gui.view.WidgetHandleView
android:id="@+id/widget_resize_handle"
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="0"
app:layout_constraintBottom_toBottomOf="@+id/widget_resize"
app:layout_constraintLeft_toLeftOf="@+id/widget_resize"
app:layout_constraintRight_toRightOf="@+id/widget_resize"
app:layout_constraintTop_toTopOf="@+id/widget_resize" />
<ImageView
android:id="@+id/resizeLongTapIcon"
android:layout_width="52dp"
android:layout_height="52dp"
android:src="?attr/ic_tips_tap"
app:layout_constraintBottom_toBottomOf="@+id/widget_resize"
app:layout_constraintEnd_toEndOf="@+id/widget_resize"
app:layout_constraintStart_toStartOf="@+id/widget_resize"
app:layout_constraintTop_toTopOf="@+id/widget_resize" />
<ImageView
android:id="@+id/imageView19"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tint="?attr/colorPrimary"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="@+id/widget_explanation_step3_text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/widget_explanation_step3_text"
app:srcCompat="@drawable/ic_theme" />
<TextView
android:id="@+id/widget_explanation_step3_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:fontFamily="sans-serif-medium"
android:text="@string/widget_explanation_end"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@+id/widget_next_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView19"
app:layout_constraintTop_toBottomOf="@+id/title" />
<Button
android:id="@+id/widget_next_button"
style="@style/Widget.MaterialComponents.Button.TextButton.Dialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:text="@string/next"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</layout>

6
application/vlc-android/res/menu/widget_configure_option.xml

@ -28,6 +28,12 @@
<item
android:orderInCategory="1"
android:id="@+id/widget_info"
android:icon="@drawable/ic_am_information"
android:title="@string/info"
vlc:showAsAction="always"/>
<item
android:orderInCategory="2"
android:id="@+id/widget_configure_done"
android:icon="@drawable/ic_options_check"
android:title="@string/done"

183
application/vlc-android/src/org/videolan/vlc/gui/dialogs/WidgetExplanationDialog.kt

@ -0,0 +1,183 @@
/*
* ************************************************************************
* WidgetExplanationDialog.kt
* *************************************************************************
* Copyright © 2022 VLC authors and VideoLAN
* Author: Nicolas POMEPUY
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
* **************************************************************************
*
*
*/
package org.videolan.vlc.gui.dialogs
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.animation.PropertyValuesHolder
import android.animation.ValueAnimator
import android.os.Bundle
import android.os.Message
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.DecelerateInterpolator
import androidx.annotation.DrawableRes
import androidx.core.animation.doOnEnd
import androidx.core.content.ContextCompat
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
import org.videolan.tools.*
import org.videolan.vlc.R
import org.videolan.vlc.databinding.DialogWidgetExplanationBinding
class WidgetExplanationDialog : VLCBottomSheetDialogFragment() {
override fun getDefaultState(): Int = STATE_EXPANDED
override fun needToManageOrientation(): Boolean = true
private lateinit var resizeAnimation: AnimatorSet
internal lateinit var binding: DialogWidgetExplanationBinding
private val handler = ResizeHandler(this)
var currentStep = 1
override fun initialFocusedView(): View = binding.title
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = DialogWidgetExplanationBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val sizeDrawbles = listOf(R.drawable.vlc_widget_mini, R.drawable.vlc_widget_micro, R.drawable.vlc_widget_pill, R.drawable.vlc_widget_macro)
displaySizeImage(sizeDrawbles[0])
handler.sendEmptyMessageDelayed(ACTION_DISPLAY_NEXT, 1000)
binding.widgetNextButton.setOnClickListener {
when (currentStep) {
1 -> {
binding.step1.setGone()
binding.step2.setVisible()
binding.step3.setGone()
animateLongTap()
}
2 -> {
binding.step1.setGone()
binding.step2.setGone()
binding.step3.setVisible()
binding.widgetNextButton.text = getString(R.string.close)
resizeAnimation.cancel()
}
else -> dismiss()
}
currentStep++
}
}
/**
* Display a new image in the UI showing the different widget sizes
*
* @param drawable
*/
private fun displaySizeImage(@DrawableRes drawable: Int) {
binding.widgetSizes.setImageDrawable(ContextCompat.getDrawable(requireActivity(), drawable))
}
override fun dismiss() {
handler.removeMessages(ACTION_DISPLAY_NEXT)
super.dismiss()
}
/**
* Animate the view showing how to resize a widget
*
*/
private fun animateLongTap() {
val show = ObjectAnimator.ofFloat(binding.resizeLongTapIcon, View.ALPHA, 1F)
show.duration = 200
show.startDelay = 2500
val tapScaleX: PropertyValuesHolder = PropertyValuesHolder.ofFloat(View.SCALE_X, 0.9F)
val tapScaleY: PropertyValuesHolder = PropertyValuesHolder.ofFloat(View.SCALE_Y, 0.9F)
val tap: ObjectAnimator = ObjectAnimator.ofPropertyValuesHolder(binding.resizeLongTapIcon, tapScaleX, tapScaleY)
tap.duration = 800
val showHandle: ObjectAnimator = ObjectAnimator.ofFloat(binding.widgetResizeHandle, View.ALPHA, 1F)
showHandle.duration = 200
val widthAnimator = ValueAnimator.ofInt(binding.widgetResize.width, 128.dp)
widthAnimator.duration = 1500
widthAnimator.interpolator = DecelerateInterpolator()
val untapScaleX: PropertyValuesHolder = PropertyValuesHolder.ofFloat(View.SCALE_X, 1F)
val untapScaleY: PropertyValuesHolder = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1F)
val untap: ObjectAnimator = ObjectAnimator.ofPropertyValuesHolder(binding.resizeLongTapIcon, untapScaleX, untapScaleY)
untap.duration = 300
val hide = ObjectAnimator.ofFloat(binding.resizeLongTapIcon, View.ALPHA, 0F)
hide.duration = 200
resizeAnimation = AnimatorSet()
resizeAnimation.playSequentially(show, tap, showHandle, untap, hide, widthAnimator)
resizeAnimation.doOnEnd { resizeAnimation.start() }
resizeAnimation.start()
}
}
private const val ACTION_DISPLAY_NEXT = 1
/**
* Manages the widget size UI loop
*
* @param owner the dialog itself
*/
private class ResizeHandler(owner: WidgetExplanationDialog) : WeakHandler<WidgetExplanationDialog>(owner) {
private val sizeDrawables = listOf(R.drawable.vlc_widget_mini, R.drawable.vlc_widget_micro, R.drawable.vlc_widget_pill, R.drawable.vlc_widget_macro)
var currentDrawable = R.drawable.vlc_widget_macro
private fun displaySizeImage(@DrawableRes drawable: Int) {
owner?.let { owner ->
owner.activity?.let {
owner.binding.widgetSizes.setImageDrawable(ContextCompat.getDrawable(it, drawable))
}
}
}
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
when (msg.what) {
ACTION_DISPLAY_NEXT -> {
removeMessages(ACTION_DISPLAY_NEXT)
val nextIndex = (sizeDrawables.indexOf(currentDrawable) + 1).coerceInOrDefault(0, sizeDrawables.size - 1, 0)
currentDrawable = sizeDrawables[nextIndex]
displaySizeImage(currentDrawable)
removeMessages(ACTION_DISPLAY_NEXT)
sendEmptyMessageDelayed(ACTION_DISPLAY_NEXT, 2000)
}
}
}
}

79
application/vlc-android/src/org/videolan/vlc/gui/view/WidgetHandleView.kt

@ -0,0 +1,79 @@
/*
* ************************************************************************
* WidgetHandleView.kt
* *************************************************************************
* Copyright © 2022 VLC authors and VideoLAN
* Author: Nicolas POMEPUY
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
* **************************************************************************
*
*
*/
package org.videolan.vlc.gui.view
import android.content.Context
import android.content.res.Resources
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import android.util.TypedValue
import android.view.View
import org.videolan.tools.dp
import org.videolan.vlc.R
class WidgetHandleView : View {
private val paint = Paint()
constructor(context: Context) : super(context) {
initialize()
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
initialize()
}
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
initialize()
}
private fun initialize() {
val typedValue = TypedValue()
val theme: Resources.Theme = context.theme
theme.resolveAttribute(R.attr.colorPrimary, typedValue, true)
paint.color = typedValue.data
paint.isAntiAlias = true
}
override fun onDraw(canvas: Canvas?) {
val padding = 8.dp.toFloat()
val viewWidth = width
//3.56 is the known ratio of the [R.drawable.vlc_widget_mini] image. 6.dp is two times the stroke size
val width = ((height - 16.dp) * 3.56) - 6.dp
val hPadding = ((viewWidth.toFloat() - width) / 2).toFloat()
val height = height - padding
paint.strokeWidth = 3.dp.toFloat()
paint.style = Paint.Style.STROKE
canvas?.drawRoundRect(hPadding, padding, viewWidth - hPadding, height, 12.dp.toFloat(), 12.dp.toFloat(), paint)
paint.style = Paint.Style.FILL
canvas?.drawCircle(hPadding, padding + (height / 2), 6.dp.toFloat(), paint)
canvas?.drawCircle(hPadding + (width.toFloat() / 2), padding, 6.dp.toFloat(), paint)
canvas?.drawCircle(hPadding + width.toFloat(), padding + (height / 2), 6.dp.toFloat(), paint)
canvas?.drawCircle(hPadding + (width.toFloat() / 2), height, 6.dp.toFloat(), paint)
super.onDraw(canvas)
}
}

22
application/vlc-android/src/org/videolan/vlc/widget/MiniPlayerConfigureActivity.kt

@ -41,6 +41,7 @@ import com.google.android.material.appbar.MaterialToolbar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.videolan.tools.PREF_WIDGETS_TIPS_SHOWN
import org.videolan.tools.Settings
import org.videolan.tools.dp
import org.videolan.tools.putSingle
@ -48,6 +49,7 @@ import org.videolan.vlc.BuildConfig
import org.videolan.vlc.R
import org.videolan.vlc.databinding.WidgetMiniPlayerConfigureBinding
import org.videolan.vlc.gui.BaseActivity
import org.videolan.vlc.gui.dialogs.WidgetExplanationDialog
import org.videolan.vlc.gui.helpers.bitmapFromView
import org.videolan.vlc.gui.preferences.widgets.PreferencesWidgets
import org.videolan.vlc.gui.preferences.widgets.WIDGET_ID
@ -115,6 +117,11 @@ class MiniPlayerConfigureActivity : BaseActivity() {
binding.previewPlaying.setOnCheckedChangeListener { _, _ -> updatePreview() }
if (!settings.getBoolean(PREF_WIDGETS_TIPS_SHOWN, false)) {
val widgetExplanationDialog = WidgetExplanationDialog()
widgetExplanationDialog.show(supportFragmentManager, "fragment_widget_explanation")
settings.putSingle(PREF_WIDGETS_TIPS_SHOWN, true)
}
}
private fun updatePreview() {
@ -148,10 +155,17 @@ class MiniPlayerConfigureActivity : BaseActivity() {
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.widget_configure_done) {
onWidgetContainerClicked()
finish()
return true
when (item.itemId) {
R.id.widget_configure_done -> {
onWidgetContainerClicked()
finish()
return true
}
R.id.widget_info -> {
val widgetExplanationDialog = WidgetExplanationDialog()
widgetExplanationDialog.show(supportFragmentManager, "fragment_widget_explanation")
return true
}
}
return super.onOptionsItemSelected(item)
}

Loading…
Cancel
Save