From 132f2802f98243c6c93ff056194e4a3668f3a091 Mon Sep 17 00:00:00 2001 From: vkay94 Date: Mon, 23 Nov 2020 20:02:58 +0100 Subject: [PATCH 1/5] Remove IDEA specific files and test classes --- .gitignore | 102 +++++++++++++-- .idea/.name | 1 - .idea/codeStyles/Project.xml | 122 ------------------ .idea/codeStyles/codeStyleConfig.xml | 5 - .idea/gradle.xml | 21 --- .idea/misc.xml | 9 -- .idea/runConfigurations.xml | 12 -- .idea/vcs.xml | 6 - .../ExampleInstrumentedTest.kt | 24 ---- .../ExampleUnitTest.kt | 17 --- .../vkay94/dtpv/ExampleInstrumentedTest.kt | 24 ---- .../com/github/vkay94/dtpv/ExampleUnitTest.kt | 17 --- 12 files changed, 89 insertions(+), 271 deletions(-) delete mode 100644 .idea/.name delete mode 100644 .idea/codeStyles/Project.xml delete mode 100644 .idea/codeStyles/codeStyleConfig.xml delete mode 100644 .idea/gradle.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/runConfigurations.xml delete mode 100644 .idea/vcs.xml delete mode 100644 app/src/androidTest/java/com/github/vkay94/doubletapplayerviewexample/ExampleInstrumentedTest.kt delete mode 100644 app/src/test/java/com/github/vkay94/doubletapplayerviewexample/ExampleUnitTest.kt delete mode 100644 doubletapplayerview/src/androidTest/java/com/github/vkay94/dtpv/ExampleInstrumentedTest.kt delete mode 100644 doubletapplayerview/src/test/java/com/github/vkay94/dtpv/ExampleUnitTest.kt diff --git a/.gitignore b/.gitignore index 3ee1aa0..32beabc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,91 @@ +# Built application files +*.apk +*.aar +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ *.iml -.gradle -/local.properties -/.idea/caches -/.idea/libraries -/.idea/modules.xml -/.idea/workspace.xml -/.idea/navEditor.xml -/.idea/assetWizardSettings.xml -.DS_Store -/build -/captures +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later .externalNativeBuild -.cxx -keystore.properties +.cxx/ + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ + +# Android Profiling +*.hprof + +# Signing +keystore.properties \ No newline at end of file diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 57a7140..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -DoubleTapPlayerViewExample \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 88ea3aa..0000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - - - - -
- - - - xmlns:android - - ^$ - - - -
-
- - - - xmlns:.* - - ^$ - - - BY_NAME - -
-
- - - - .*:id - - http://schemas.android.com/apk/res/android - - - -
-
- - - - .*:name - - http://schemas.android.com/apk/res/android - - - -
-
- - - - name - - ^$ - - - -
-
- - - - style - - ^$ - - - -
-
- - - - .* - - ^$ - - - BY_NAME - -
-
- - - - .* - - http://schemas.android.com/apk/res/android - - - ANDROID_ATTRIBUTE_ORDER - -
-
- - - - .* - - .* - - - BY_NAME - -
-
-
-
- - -
-
\ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index 79ee123..0000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 1a3561d..0000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 7bfef59..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460..0000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/androidTest/java/com/github/vkay94/doubletapplayerviewexample/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/github/vkay94/doubletapplayerviewexample/ExampleInstrumentedTest.kt deleted file mode 100644 index 5a9d544..0000000 --- a/app/src/androidTest/java/com/github/vkay94/doubletapplayerviewexample/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.github.vkay94.doubletapplayerviewexample - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.github.vkay94.doubletapplayerviewexample", appContext.packageName) - } -} diff --git a/app/src/test/java/com/github/vkay94/doubletapplayerviewexample/ExampleUnitTest.kt b/app/src/test/java/com/github/vkay94/doubletapplayerviewexample/ExampleUnitTest.kt deleted file mode 100644 index b57e238..0000000 --- a/app/src/test/java/com/github/vkay94/doubletapplayerviewexample/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.github.vkay94.doubletapplayerviewexample - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} diff --git a/doubletapplayerview/src/androidTest/java/com/github/vkay94/dtpv/ExampleInstrumentedTest.kt b/doubletapplayerview/src/androidTest/java/com/github/vkay94/dtpv/ExampleInstrumentedTest.kt deleted file mode 100644 index eb03835..0000000 --- a/doubletapplayerview/src/androidTest/java/com/github/vkay94/dtpv/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.github.vkay94.dtpv - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.github.vkay94.dtpv.test", appContext.packageName) - } -} diff --git a/doubletapplayerview/src/test/java/com/github/vkay94/dtpv/ExampleUnitTest.kt b/doubletapplayerview/src/test/java/com/github/vkay94/dtpv/ExampleUnitTest.kt deleted file mode 100644 index b0ac80d..0000000 --- a/doubletapplayerview/src/test/java/com/github/vkay94/dtpv/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.github.vkay94.dtpv - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} From 0cd998c8e8765cae072150f5e2f9399ca682bcd0 Mon Sep 17 00:00:00 2001 From: vkay94 Date: Mon, 23 Nov 2020 20:16:47 +0100 Subject: [PATCH 2/5] Moved from Kotlin Android Extension to ViewBinding and updated dependencies --- app/build.gradle | 11 ++- .../BaseVideoActivity.kt | 53 +++++------ .../DataAndUtils.kt | 4 +- .../MainActivity.kt | 74 ++++++++------- .../fragments/SecondsViewFragment.kt | 41 ++++---- .../fragments/SectionsPagerAdapter.kt | 4 +- .../fragments/ShapeFragment.kt | 27 +++--- .../layout/exo_playback_control_view_yt.xml | 18 ++-- .../main/res/layout/fragment_seconds_view.xml | 35 +++---- app/src/main/res/layout/fragment_shape.xml | 30 +----- build.gradle | 12 +-- doubletapplayerview/build.gradle | 1 - .../vkay94/dtpv/youtube/YouTubeOverlay.kt | 93 ++++++++++--------- .../dtpv/youtube/views/YouTubeSecondsView.kt | 85 ++++++++++------- gradle/wrapper/gradle-wrapper.properties | 4 +- 15 files changed, 257 insertions(+), 235 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index bfb732b..b3f7162 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,9 +1,6 @@ apply plugin: 'com.android.application' - apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' - android { compileSdkVersion 29 buildToolsVersion '29.0.3' @@ -14,6 +11,7 @@ android { versionCode libVersionCode versionName "$libMajor.$libMinor.$libPatch" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + multiDexEnabled true } signingConfigs { release { @@ -43,6 +41,10 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + + buildFeatures { + viewBinding true + } } dependencies { @@ -59,6 +61,7 @@ dependencies { implementation "com.github.StephenVinouze:MaterialNumberPicker:1.1.0" implementation project(path: ':doubletapplayerview') - implementation 'com.google.android.material:material:1.1.0' + implementation 'com.google.android.material:material:1.2.1' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + implementation 'androidx.multidex:multidex:2.0.1' } diff --git a/app/src/main/java/com/github/vkay94/doubletapplayerviewexample/BaseVideoActivity.kt b/app/src/main/java/com/github/vkay94/doubletapplayerviewexample/BaseVideoActivity.kt index 5c4ba3f..43f7ff2 100644 --- a/app/src/main/java/com/github/vkay94/doubletapplayerviewexample/BaseVideoActivity.kt +++ b/app/src/main/java/com/github/vkay94/doubletapplayerviewexample/BaseVideoActivity.kt @@ -5,13 +5,11 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.os.Bundle +import android.view.View import android.view.WindowManager import androidx.appcompat.app.AppCompatActivity import com.github.vkay94.dtpv.DoubleTapPlayerView -import com.google.android.exoplayer2.DefaultLoadControl -import com.google.android.exoplayer2.LoadControl -import com.google.android.exoplayer2.Player -import com.google.android.exoplayer2.SimpleExoPlayer +import com.google.android.exoplayer2.* import com.google.android.exoplayer2.source.ProgressiveMediaSource import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory @@ -36,10 +34,12 @@ open class BaseVideoActivity : AppCompatActivity() { DefaultBandwidthMeter.Builder(this@BaseVideoActivity).build() ) val videoSource = ProgressiveMediaSource.Factory(dataSourceFactory, Mp4ExtractorFactory()) - .createMediaSource(mUri) + .createMediaSource(MediaItem.fromUri(mUri)) - player?.prepare(videoSource) - player?.playWhenReady = true + player?.apply { + setMediaSource(videoSource) + prepare() + } } fun initializePlayer() { @@ -51,7 +51,7 @@ open class BaseVideoActivity : AppCompatActivity() { MIN_PLAYBACK_START_BUFFER, MIN_PLAYBACK_RESUME_BUFFER ) - .createDefaultLoadControl() + .build() player = SimpleExoPlayer.Builder(this) .setLoadControl(loadControl) @@ -69,48 +69,39 @@ open class BaseVideoActivity : AppCompatActivity() { } } - fun pausePlayer() { - if (player != null) { - player?.playWhenReady = false - player?.playbackState - } - } - - fun resumePlayer() { - if (player != null) { - player?.playWhenReady = true - player?.playbackState - } - } - override fun onPause() { super.onPause() - pausePlayer() + player?.pause() } override fun onRestart() { super.onRestart() if (player?.playbackState == Player.STATE_READY && player?.playWhenReady!!) - resumePlayer() + player?.play() } override fun onDestroy() { super.onDestroy() - - if (player != null) { - player?.release() - player = null - } + releasePlayer() } fun setFullscreen(fullscreen: Boolean) { if (fullscreen) { - this.window.setFlags( + window.setFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN ) + window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE } else { - this.window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) + window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_FULLSCREEN + and View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + and View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) + } else { + window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_FULLSCREEN + and View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) + } } } diff --git a/app/src/main/java/com/github/vkay94/doubletapplayerviewexample/DataAndUtils.kt b/app/src/main/java/com/github/vkay94/doubletapplayerviewexample/DataAndUtils.kt index 15f7f41..34e28d6 100644 --- a/app/src/main/java/com/github/vkay94/doubletapplayerviewexample/DataAndUtils.kt +++ b/app/src/main/java/com/github/vkay94/doubletapplayerviewexample/DataAndUtils.kt @@ -9,9 +9,9 @@ import android.util.TypedValue object DataAndUtils { /** - * This is a selected list of sample videos for demonstration + * This is a selected list of sample videos for demonstration. * - * Source: [Github Gist](https://gist.github.com/jsturgis/3b19447b304616f18657). + * Source: [Github Gist](https://gist.github.com/jsturgis/3b19447b304616f18657) */ val videoList = listOf( "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", diff --git a/app/src/main/java/com/github/vkay94/doubletapplayerviewexample/MainActivity.kt b/app/src/main/java/com/github/vkay94/doubletapplayerviewexample/MainActivity.kt index cd42b46..ef1be6a 100644 --- a/app/src/main/java/com/github/vkay94/doubletapplayerviewexample/MainActivity.kt +++ b/app/src/main/java/com/github/vkay94/doubletapplayerviewexample/MainActivity.kt @@ -8,29 +8,45 @@ import android.view.Menu import android.view.MenuItem import android.view.View import android.widget.Toast -import androidx.lifecycle.Observer +import androidx.constraintlayout.widget.ConstraintLayout import androidx.lifecycle.ViewModelProvider +import com.github.vkay94.doubletapplayerviewexample.databinding.ActivityMainBinding +import com.github.vkay94.doubletapplayerviewexample.databinding.ExoPlaybackControlViewYtBinding import com.github.vkay94.doubletapplayerviewexample.fragments.PageViewModel import com.github.vkay94.doubletapplayerviewexample.fragments.SecondsViewFragment import com.github.vkay94.doubletapplayerviewexample.fragments.SectionsPagerAdapter import com.github.vkay94.doubletapplayerviewexample.fragments.ShapeFragment import com.github.vkay94.dtpv.youtube.YouTubeOverlay -import kotlinx.android.synthetic.main.activity_main.* -import kotlinx.android.synthetic.main.exo_playback_control_view_yt.* class MainActivity : BaseVideoActivity() { private var isVideoFullscreen = false private var currentVideoId = -1 + // View bindings + private lateinit var binding: ActivityMainBinding + private lateinit var controlsBinding: ExoPlaybackControlViewYtBinding + private lateinit var viewModel: PageViewModel + // Views (to eliminate repeating 'binding.'-prefixes) + private lateinit var ytOverlay: YouTubeOverlay + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - setSupportActionBar(toolbar) - this.videoPlayer = previewPlayerView + // Setup layout views with View binding + binding = ActivityMainBinding.inflate(layoutInflater) + val view = binding.root + val controls = view.findViewById(R.id.exo_controls_root) + controlsBinding = ExoPlaybackControlViewYtBinding.bind(controls) + setContentView(view) + + ytOverlay = binding.ytOverlay + videoPlayer = binding.previewPlayerView + + setSupportActionBar(binding.toolbar) + initDoubleTapPlayerView() initViewModel() startNextVideo() @@ -39,10 +55,10 @@ class MainActivity : BaseVideoActivity() { sectionsPagerAdapter.addFragment(ShapeFragment.newInstance()) sectionsPagerAdapter.addFragment(SecondsViewFragment.newInstance()) - view_pager.adapter = sectionsPagerAdapter - tabs.setupWithViewPager(view_pager) + binding.viewPager.adapter = sectionsPagerAdapter + binding.tabs.setupWithViewPager(binding.viewPager) - fullscreen_button.setOnClickListener { + controlsBinding.fullscreenButton.setOnClickListener { toggleFullscreen() } } @@ -53,16 +69,16 @@ class MainActivity : BaseVideoActivity() { //.playerView(previewPlayerView) .performListener(object : YouTubeOverlay.PerformListener { override fun onAnimationStart() { - previewPlayerView.useController = false + binding.previewPlayerView.useController = false ytOverlay.visibility = View.VISIBLE } override fun onAnimationEnd() { ytOverlay.visibility = View.GONE - previewPlayerView.useController = true + binding.previewPlayerView.useController = true } }) - previewPlayerView.doubleTapDelay = 800 + binding.previewPlayerView.doubleTapDelay = 800 // Uncomment this line if the PlayerDoubleTapListener is not set via XML // previewPlayerView.controller(ytOverlay) } @@ -78,28 +94,28 @@ class MainActivity : BaseVideoActivity() { iconSpeed.value = ytOverlay.iconAnimationDuration } - viewModel.circleExpandDuration.observe(this, Observer { + viewModel.circleExpandDuration.observe(this, { ytOverlay.animationDuration(it) }) - viewModel.arcSize.observe(this, Observer { + viewModel.arcSize.observe(this, { ytOverlay.arcSize(DataAndUtils.dpToPx(this@MainActivity, it.toFloat())) }) - viewModel.fontSize.observe(this, Observer { + viewModel.fontSize.observe(this, { TextViewStyler().textSize(it).applyTo(ytOverlay.secondsTextView) }) - viewModel.typeFace.observe(this, Observer { + viewModel.typeFace.observe(this, { TextViewStyler().textStyle(it).applyTo(ytOverlay.secondsTextView) }) - viewModel.tapCircleColor.observe(this, Observer { + viewModel.tapCircleColor.observe(this, { ytOverlay.tapCircleColorInt(it) }) - viewModel.circleBackgroundColor.observe(this, Observer { + viewModel.circleBackgroundColor.observe(this, { ytOverlay.circleBackgroundColorInt(it) }) - viewModel.secondsIcon.observe(this, Observer { + viewModel.secondsIcon.observe(this, { ytOverlay.icon(it) }) - viewModel.iconSpeed.observe(this, Observer { + viewModel.iconSpeed.observe(this, { ytOverlay.iconAnimationDuration(it) }) } @@ -111,27 +127,23 @@ class MainActivity : BaseVideoActivity() { currentVideoId = (currentVideoId + 1).rem(DataAndUtils.videoList.size) buildMediaSource(Uri.parse(DataAndUtils.videoList[currentVideoId])) + player?.play() } private fun toggleFullscreen() { if (isVideoFullscreen) { setFullscreen(false) - window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE; - if(supportActionBar != null){ - supportActionBar?.show(); + if (supportActionBar != null) { + supportActionBar?.show() } - requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT isVideoFullscreen = false } else { setFullscreen(true) - window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_FULLSCREEN - and View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - and View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) - - if(supportActionBar != null){ - supportActionBar?.hide(); + if (supportActionBar != null) { + supportActionBar?.hide() } - requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE isVideoFullscreen = true } } diff --git a/app/src/main/java/com/github/vkay94/doubletapplayerviewexample/fragments/SecondsViewFragment.kt b/app/src/main/java/com/github/vkay94/doubletapplayerviewexample/fragments/SecondsViewFragment.kt index faf0895..02ced81 100644 --- a/app/src/main/java/com/github/vkay94/doubletapplayerviewexample/fragments/SecondsViewFragment.kt +++ b/app/src/main/java/com/github/vkay94/doubletapplayerviewexample/fragments/SecondsViewFragment.kt @@ -13,42 +13,46 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import com.github.vkay94.doubletapplayerviewexample.R import com.github.vkay94.doubletapplayerviewexample.TextViewStyler -import kotlinx.android.synthetic.main.fragment_seconds_view.* +import com.github.vkay94.doubletapplayerviewexample.databinding.FragmentSecondsViewBinding class SecondsViewFragment : Fragment() { + private var _binding: FragmentSecondsViewBinding? = null + private val binding get() = _binding!! + private lateinit var viewModel: PageViewModel override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.fragment_seconds_view, container, false) + ): View { + _binding = FragmentSecondsViewBinding.inflate(inflater, container, false) + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - seconds_view.seconds = 10 - seconds_view.textView.setTextColor(Color.WHITE) + binding.secondsView.seconds = 10 + binding.secondsView.textView.setTextColor(Color.WHITE) - seconds_view.start() + binding.secondsView.start() activity?.let { fa -> viewModel = ViewModelProvider(fa).get(PageViewModel::class.java) - textsize_picker.apply { + binding.textsizePicker.apply { minValue = 10 maxValue = 20 value = 13 setFormatter { "$it sp" } setOnValueChangedListener { picker, _, _ -> - seconds_view.textView.textSize = picker.value.toFloat() + binding.secondsView.textView.textSize = picker.value.toFloat() viewModel.fontSize.value = picker.value.toFloat() } } - typeface_picker.apply { + binding.typefacePicker.apply { minValue = 0 maxValue = 3 value = 0 @@ -71,12 +75,12 @@ class SecondsViewFragment : Fragment() { } TextViewStyler() - .textStyle(tf).applyTo(seconds_view.textView) + .textStyle(tf).applyTo(binding.secondsView.textView) viewModel.typeFace.value = tf } } - seconds_picker.apply { + binding.secondsPicker.apply { minValue = 0 maxValue = 3 value = 0 @@ -99,7 +103,7 @@ class SecondsViewFragment : Fragment() { } if (res > 0) { - seconds_view.icon = res + binding.secondsView.icon = res viewModel.secondsIcon.value = res } } @@ -107,13 +111,13 @@ class SecondsViewFragment : Fragment() { } initTriangleSpeedSeekbar( - seekbar_youtube_drawable_animation_duration, - tv_youtube_drawable_animation_duration, + binding.seekbarYoutubeDrawableAnimationDuration, + binding.tvYoutubeDrawableAnimationDuration, 3000, 500, - seconds_view.cycleDuration.toInt() ?: 800 + binding.secondsView.cycleDuration.toInt() ) { - seconds_view.cycleDuration = it.toLong() + binding.secondsView.cycleDuration = it.toLong() viewModel.iconSpeed.value = it.toLong() } } @@ -145,6 +149,11 @@ class SecondsViewFragment : Fragment() { } } + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + companion object { @JvmStatic fun newInstance(): SecondsViewFragment { diff --git a/app/src/main/java/com/github/vkay94/doubletapplayerviewexample/fragments/SectionsPagerAdapter.kt b/app/src/main/java/com/github/vkay94/doubletapplayerviewexample/fragments/SectionsPagerAdapter.kt index 01bc641..f8105e5 100644 --- a/app/src/main/java/com/github/vkay94/doubletapplayerviewexample/fragments/SectionsPagerAdapter.kt +++ b/app/src/main/java/com/github/vkay94/doubletapplayerviewexample/fragments/SectionsPagerAdapter.kt @@ -16,7 +16,7 @@ private val TAB_TITLES = arrayOf( * one of the sections/tabs/pages. */ class SectionsPagerAdapter(private val context: Context, fm: FragmentManager) : - FragmentPagerAdapter(fm) { + FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { private val fragmentList: ArrayList = arrayListOf() @@ -26,7 +26,7 @@ class SectionsPagerAdapter(private val context: Context, fm: FragmentManager) : override fun getItem(position: Int): Fragment = fragmentList[position] - override fun getPageTitle(position: Int): CharSequence? = + override fun getPageTitle(position: Int): CharSequence = context.resources.getString(TAB_TITLES[position]) override fun getCount(): Int = fragmentList.size diff --git a/app/src/main/java/com/github/vkay94/doubletapplayerviewexample/fragments/ShapeFragment.kt b/app/src/main/java/com/github/vkay94/doubletapplayerviewexample/fragments/ShapeFragment.kt index 9934b13..75e6fe0 100644 --- a/app/src/main/java/com/github/vkay94/doubletapplayerviewexample/fragments/ShapeFragment.kt +++ b/app/src/main/java/com/github/vkay94/doubletapplayerviewexample/fragments/ShapeFragment.kt @@ -10,14 +10,16 @@ import android.widget.TextView import androidx.appcompat.widget.AppCompatSeekBar import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider -import com.github.vkay94.doubletapplayerviewexample.R -import kotlinx.android.synthetic.main.fragment_shape.* +import com.github.vkay94.doubletapplayerviewexample.databinding.FragmentShapeBinding /** * A placeholder fragment containing a simple view. */ class ShapeFragment : Fragment() { + private var _binding: FragmentShapeBinding? = null + private val binding get() = _binding!! + private lateinit var viewModel: PageViewModel private val minAnimationDuration = 500 @@ -31,8 +33,9 @@ class ShapeFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.fragment_shape, container, false) + ): View { + _binding = FragmentShapeBinding.inflate(inflater, container, false) + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -42,8 +45,8 @@ class ShapeFragment : Fragment() { viewModel = ViewModelProvider(fa).get(PageViewModel::class.java) initializeDurationSeekBar( - seekbar_youtube_animation_duration, - tv_youtube_animation_duration, + binding.seekbarYoutubeAnimationDuration, + binding.tvYoutubeAnimationDuration, maxAnimationDuration, minAnimationDuration, viewModel.circleExpandDuration.value?.toInt() ?: 800 @@ -52,8 +55,8 @@ class ShapeFragment : Fragment() { } initializeDimensSeekBar( - seekbar_arc_size, - tv_arc_size, + binding.seekbarArcSize, + binding.tvArcSize, maxArcSize, minArcSize, viewModel.arcSize.value?.toInt() ?: 40 @@ -62,8 +65,8 @@ class ShapeFragment : Fragment() { } initializeAlphaSeekbar( - seekbar_tap_circle_alpha, - tv_tap_circle_alpha, + binding.seekbarTapCircleAlpha, + binding.tvTapCircleAlpha, 100, 0, (Color.alpha(viewModel.tapCircleColor.value ?: 10) * (100 / 255f)).toInt() ) { @@ -71,8 +74,8 @@ class ShapeFragment : Fragment() { } initializeAlphaSeekbar( - seekbar_background_circle_alpha, - tv_background_circle_alpha, + binding.seekbarBackgroundCircleAlpha, + binding.tvBackgroundCircleAlpha, 100, 0, (Color.alpha(viewModel.circleBackgroundColor.value ?: 15) * (100 / 255f)).toInt() ) { diff --git a/app/src/main/res/layout/exo_playback_control_view_yt.xml b/app/src/main/res/layout/exo_playback_control_view_yt.xml index 335cbd9..26a7786 100644 --- a/app/src/main/res/layout/exo_playback_control_view_yt.xml +++ b/app/src/main/res/layout/exo_playback_control_view_yt.xml @@ -2,6 +2,7 @@ @@ -33,17 +34,17 @@ + app:layout_constraintEnd_toStartOf="@id/fullscreen_button" + app:layout_constraintStart_toStartOf="parent"> + tools:ignore="ContentDescription" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_seconds_view.xml b/app/src/main/res/layout/fragment_seconds_view.xml index d8cb046..7fb001a 100644 --- a/app/src/main/res/layout/fragment_seconds_view.xml +++ b/app/src/main/res/layout/fragment_seconds_view.xml @@ -4,13 +4,14 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:scrollbars="none"> + android:scrollbars="none" + tools:ignore="HardcodedText"> + android:orientation="vertical" + android:padding="16dp"> - + - + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_shape.xml b/app/src/main/res/layout/fragment_shape.xml index c9827b6..59ed4d1 100644 --- a/app/src/main/res/layout/fragment_shape.xml +++ b/app/src/main/res/layout/fragment_shape.xml @@ -4,7 +4,8 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:scrollbars="none"> + android:scrollbars="none" + tools:ignore="HardcodedText"> - - - - - - - @@ -62,17 +56,10 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_arc_size" /> - - - - - - - - - - - - - - diff --git a/build.gradle b/build.gradle index d57344b..d09106f 100644 --- a/build.gradle +++ b/build.gradle @@ -8,11 +8,11 @@ buildscript { libVersionCode = libPatch + (libMinor * 100) + (libMajor * 10000) - kotlin_version = '1.3.72' - core_ktx_version = '1.3.0' - exoplayer_version = '2.11.7' - appcompat_version = '1.1.0' - constraintlayout_version = '1.1.3' + kotlin_version = '1.4.20' + core_ktx_version = '1.3.2' + exoplayer_version = '2.12.1' + appcompat_version = '1.2.0' + constraintlayout_version = '2.0.4' } repositories { @@ -21,7 +21,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:4.0.1' + classpath 'com.android.tools.build:gradle:4.1.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/doubletapplayerview/build.gradle b/doubletapplayerview/build.gradle index d6eecf5..eaa8cba 100644 --- a/doubletapplayerview/build.gradle +++ b/doubletapplayerview/build.gradle @@ -1,6 +1,5 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' android { compileSdkVersion 29 diff --git a/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/youtube/YouTubeOverlay.kt b/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/youtube/YouTubeOverlay.kt index cd5817f..ff775f9 100644 --- a/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/youtube/YouTubeOverlay.kt +++ b/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/youtube/YouTubeOverlay.kt @@ -14,8 +14,9 @@ import com.github.vkay94.dtpv.DoubleTapPlayerView import com.github.vkay94.dtpv.PlayerDoubleTapListener import com.github.vkay94.dtpv.R import com.github.vkay94.dtpv.SeekListener +import com.github.vkay94.dtpv.youtube.views.CircleClipTapView +import com.github.vkay94.dtpv.youtube.views.SecondsView import com.google.android.exoplayer2.Player -import kotlinx.android.synthetic.main.yt_overlay.view.* /** @@ -26,10 +27,14 @@ import kotlinx.android.synthetic.main.yt_overlay.view.* * which can't be accomplished with the regular Android Ripple (I didn't find any options in the * documentation ...). */ -class YouTubeOverlay(context: Context?, private val attrs: AttributeSet?) : +class YouTubeOverlay(context: Context, private val attrs: AttributeSet?) : ConstraintLayout(context, attrs), PlayerDoubleTapListener { - constructor(context: Context?) : this(context, null) { + private var rootLayout: ConstraintLayout + private var secondsView: SecondsView + private var circleClipTapView: CircleClipTapView + + constructor(context: Context) : this(context, null) { // Hide overlay initially when added programmatically this.visibility = View.INVISIBLE } @@ -43,18 +48,22 @@ class YouTubeOverlay(context: Context?, private val attrs: AttributeSet?) : init { LayoutInflater.from(context).inflate(R.layout.yt_overlay, this, true) + rootLayout = findViewById(R.id.root_constraint_layout) + secondsView = findViewById(R.id.seconds_view) + circleClipTapView = findViewById(R.id.circle_clip_tap_view) + // Initialize UI components initializeAttributes() - seconds_view.isForward = true + secondsView.isForward = true changeConstraints(true) // This code snippet is executed when the circle scale animation is finished - circle_clip_tap_view.performAtEnd = { + circleClipTapView.performAtEnd = { performListener?.onAnimationEnd() - seconds_view.visibility = View.INVISIBLE - seconds_view.seconds = 0 - seconds_view.stop() + secondsView.visibility = View.INVISIBLE + secondsView.seconds = 0 + secondsView.stop() } } @@ -187,9 +196,9 @@ class YouTubeOverlay(context: Context?, private val attrs: AttributeSet?) : * Color of the scaling circle on touch feedback. */ var tapCircleColor: Int - get() = circle_clip_tap_view.circleColor + get() = circleClipTapView.circleColor private set(value) { - circle_clip_tap_view.circleColor = value + circleClipTapView.circleColor = value } fun tapCircleColorRes(@ColorRes resId: Int) = apply { @@ -204,9 +213,9 @@ class YouTubeOverlay(context: Context?, private val attrs: AttributeSet?) : * Color of the clipped background circle */ var circleBackgroundColor: Int - get() = circle_clip_tap_view.circleBackgroundColor + get() = circleClipTapView.circleBackgroundColor private set(value) { - circle_clip_tap_view.circleBackgroundColor = value + circleClipTapView.circleBackgroundColor = value } fun circleBackgroundColorRes(@ColorRes resId: Int) = apply { @@ -222,9 +231,9 @@ class YouTubeOverlay(context: Context?, private val attrs: AttributeSet?) : * The overlay keeps visible until the animation finishes. */ var animationDuration: Long - get() = circle_clip_tap_view.animationDuration + get() = circleClipTapView.animationDuration private set(value) { - circle_clip_tap_view.animationDuration = value + circleClipTapView.animationDuration = value } fun animationDuration(duration: Long) = apply { @@ -236,9 +245,9 @@ class YouTubeOverlay(context: Context?, private val attrs: AttributeSet?) : * The greater the value the more roundish the shape becomes */ var arcSize: Float - get() = circle_clip_tap_view.arcSize + get() = circleClipTapView.arcSize internal set(value) { - circle_clip_tap_view.arcSize = value + circleClipTapView.arcSize = value } fun arcSize(@DimenRes resId: Int) = apply { @@ -253,9 +262,9 @@ class YouTubeOverlay(context: Context?, private val attrs: AttributeSet?) : * Duration the icon animation (fade in + fade out) for a full cycle in milliseconds. */ var iconAnimationDuration: Long = 750 - get() = seconds_view.cycleDuration + get() = secondsView.cycleDuration private set(value) { - seconds_view.cycleDuration = value + secondsView.cycleDuration = value field = value } @@ -272,10 +281,10 @@ class YouTubeOverlay(context: Context?, private val attrs: AttributeSet?) : */ @DrawableRes var icon: Int = 0 - get() = seconds_view.icon + get() = secondsView.icon private set(value) { - seconds_view.stop() - seconds_view.icon = value + secondsView.stop() + secondsView.icon = value field = value } @@ -289,7 +298,7 @@ class YouTubeOverlay(context: Context?, private val attrs: AttributeSet?) : @StyleRes var textAppearance: Int = 0 private set(value) { - TextViewCompat.setTextAppearance(seconds_view.textView, value) + TextViewCompat.setTextAppearance(secondsView.textView, value) field = value } @@ -303,7 +312,7 @@ class YouTubeOverlay(context: Context?, private val attrs: AttributeSet?) : * In case of you'd like to change some specific attributes of the TextView in runtime. */ val secondsTextView: TextView - get() = seconds_view.textView + get() = secondsView.textView override fun onDoubleTapProgressUp(posX: Float, posY: Float) { @@ -324,8 +333,8 @@ class YouTubeOverlay(context: Context?, private val attrs: AttributeSet?) : if (this.visibility != View.VISIBLE) { if (posX < playerView?.width!! * 0.35 || posX > playerView?.width!! * 0.65) { performListener?.onAnimationStart() - seconds_view.visibility = View.VISIBLE - seconds_view.start() + secondsView.visibility = View.VISIBLE + secondsView.start() } else return } @@ -334,9 +343,9 @@ class YouTubeOverlay(context: Context?, private val attrs: AttributeSet?) : posX < playerView?.width!! * 0.35 -> { // First time tap or switched - if (seconds_view.isForward) { + if (secondsView.isForward) { changeConstraints(false) - seconds_view.apply { + secondsView.apply { isForward = false seconds = 0 } @@ -344,17 +353,17 @@ class YouTubeOverlay(context: Context?, private val attrs: AttributeSet?) : // Cancel ripple and start new without triggering overlay disappearance // (resetting instead of ending) - circle_clip_tap_view.resetAnimation { - circle_clip_tap_view.updatePosition(posX, posY) + circleClipTapView.resetAnimation { + circleClipTapView.updatePosition(posX, posY) } rewinding() } posX > playerView?.width!! * 0.65 -> { // First time tap or switched - if (!seconds_view.isForward) { + if (!secondsView.isForward) { changeConstraints(true) - seconds_view.apply { + secondsView.apply { isForward = true seconds = 0 } @@ -362,8 +371,8 @@ class YouTubeOverlay(context: Context?, private val attrs: AttributeSet?) : // Cancel ripple and start new without triggering overlay disappearance // (resetting instead of ending) - circle_clip_tap_view.resetAnimation { - circle_clip_tap_view.updatePosition(posX, posY) + circleClipTapView.resetAnimation { + circleClipTapView.updatePosition(posX, posY) } forwarding() } @@ -411,30 +420,30 @@ class YouTubeOverlay(context: Context?, private val attrs: AttributeSet?) : } private fun forwarding() { - seconds_view.seconds += seekSeconds + secondsView.seconds += seekSeconds seekToPosition(player?.currentPosition?.plus(seekSeconds * 1000)) } private fun rewinding() { - seconds_view.seconds += seekSeconds + secondsView.seconds += seekSeconds seekToPosition(player?.currentPosition?.minus(seekSeconds * 1000)) } private fun changeConstraints(forward: Boolean) { val constraintSet = ConstraintSet() with(constraintSet) { - clone(root_constraint_layout) + clone(rootLayout) if (forward) { - clear(seconds_view.id, ConstraintSet.START) - connect(seconds_view.id, ConstraintSet.END, + clear(secondsView.id, ConstraintSet.START) + connect(secondsView.id, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END) } else { - clear(seconds_view.id, ConstraintSet.END) - connect(seconds_view.id, ConstraintSet.START, + clear(secondsView.id, ConstraintSet.END) + connect(secondsView.id, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START) } - seconds_view.start() - applyTo(root_constraint_layout) + secondsView.start() + applyTo(rootLayout) } } diff --git a/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/youtube/views/YouTubeSecondsView.kt b/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/youtube/views/YouTubeSecondsView.kt index a7a8dad..1a62ade 100644 --- a/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/youtube/views/YouTubeSecondsView.kt +++ b/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/youtube/views/YouTubeSecondsView.kt @@ -4,13 +4,14 @@ import android.animation.ValueAnimator import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater +import android.widget.ImageView +import android.widget.LinearLayout import android.widget.TextView import androidx.annotation.DrawableRes import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.animation.doOnEnd import androidx.core.animation.doOnStart import com.github.vkay94.dtpv.R -import kotlinx.android.synthetic.main.yt_seconds_view.view.* /** * Layout group which handles the icon animation while forwarding and rewinding. @@ -20,9 +21,25 @@ import kotlinx.android.synthetic.main.yt_seconds_view.view.* * * Used by [YouTubeOverlay][com.github.vkay94.dtpv.youtube.YouTubeOverlay]. */ -class SecondsView(context: Context?, attrs: AttributeSet?) : +class SecondsView(context: Context, attrs: AttributeSet?) : ConstraintLayout(context, attrs) { + private var trianglesContainer: LinearLayout + private var secondsTextView: TextView + private var icon1: ImageView + private var icon2: ImageView + private var icon3: ImageView + + init { + LayoutInflater.from(context).inflate(R.layout.yt_seconds_view, this, true) + + trianglesContainer = findViewById(R.id.triangle_container) + secondsTextView = findViewById(R.id.tv_seconds) + icon1 = findViewById(R.id.icon_1) + icon2 = findViewById(R.id.icon_2) + icon3 = findViewById(R.id.icon_3) + } + /** * Defines the duration for a full cycle of the triangle animation. * Each animation step takes 20% of it. @@ -42,7 +59,7 @@ class SecondsView(context: Context?, attrs: AttributeSet?) : */ var seconds: Int = 0 set(value) { - tv_seconds.text = context.resources.getQuantityString( + secondsTextView.text = context.resources.getQuantityString( R.plurals.quick_seek_x_second, value, value ) field = value @@ -53,28 +70,24 @@ class SecondsView(context: Context?, attrs: AttributeSet?) : */ var isForward: Boolean = true set(value) { - triangle_container.rotation = if (value) 0f else 180f + trianglesContainer.rotation = if (value) 0f else 180f field = value } val textView: TextView - get() = tv_seconds + get() = secondsTextView @DrawableRes var icon: Int = R.drawable.ic_play_triangle set(value) { if (value > 0) { - icon_1.setImageResource(value) - icon_2.setImageResource(value) - icon_3.setImageResource(value) + icon1.setImageResource(value) + icon2.setImageResource(value) + icon3.setImageResource(value) } field = value } - init { - LayoutInflater.from(context).inflate(R.layout.yt_seconds_view, this, true) - } - /** * Starts the triangle animation */ @@ -96,18 +109,18 @@ class SecondsView(context: Context?, attrs: AttributeSet?) : } private fun reset() { - icon_1.alpha = 0f - icon_2.alpha = 0f - icon_3.alpha = 0f + icon1.alpha = 0f + icon2.alpha = 0f + icon3.alpha = 0f } private val firstAnimator: ValueAnimator = CustomValueAnimator( { - icon_1.alpha = 0f - icon_2.alpha = 0f - icon_3.alpha = 0f + icon1.alpha = 0f + icon2.alpha = 0f + icon3.alpha = 0f }, { - icon_1.alpha = it + icon1.alpha = it }, { secondAnimator.start() } @@ -115,11 +128,11 @@ class SecondsView(context: Context?, attrs: AttributeSet?) : private val secondAnimator: ValueAnimator = CustomValueAnimator( { - icon_1.alpha = 1f - icon_2.alpha = 0f - icon_3.alpha = 0f + icon1.alpha = 1f + icon2.alpha = 0f + icon3.alpha = 0f }, { - icon_2.alpha = it + icon2.alpha = it }, { thirdAnimator.start() } @@ -127,12 +140,12 @@ class SecondsView(context: Context?, attrs: AttributeSet?) : private val thirdAnimator: ValueAnimator = CustomValueAnimator( { - icon_1.alpha = 1f - icon_2.alpha = 1f - icon_3.alpha = 0f + icon1.alpha = 1f + icon2.alpha = 1f + icon3.alpha = 0f }, { - icon_1.alpha = 1f - icon_3.alpha // or 1f - it (t3.alpha => all three stay a little longer together) - icon_3.alpha = it + icon1.alpha = 1f - icon3.alpha // or 1f - it (t3.alpha => all three stay a little longer together) + icon3.alpha = it }, { fourthAnimator.start() } @@ -140,11 +153,11 @@ class SecondsView(context: Context?, attrs: AttributeSet?) : private val fourthAnimator: ValueAnimator = CustomValueAnimator( { - icon_1.alpha = 0f - icon_2.alpha = 1f - icon_3.alpha = 1f + icon1.alpha = 0f + icon2.alpha = 1f + icon3.alpha = 1f }, { - icon_2.alpha = 1f - it + icon2.alpha = 1f - it }, { fifthAnimator.start() } @@ -152,11 +165,11 @@ class SecondsView(context: Context?, attrs: AttributeSet?) : private val fifthAnimator: ValueAnimator = CustomValueAnimator( { - icon_1.alpha = 0f - icon_2.alpha = 0f - icon_3.alpha = 1f + icon1.alpha = 0f + icon2.alpha = 0f + icon3.alpha = 1f }, { - icon_3.alpha = 1f - it + icon3.alpha = 1f - it }, { firstAnimator.start() } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d198059..b176a45 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Jul 05 19:47:33 CEST 2020 +#Sun Nov 22 23:37:54 CET 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip From ad33bedcf930bf6df53d3228d00b370cc0af38fc Mon Sep 17 00:00:00 2001 From: vkay94 Date: Mon, 23 Nov 2020 20:20:25 +0100 Subject: [PATCH 3/5] YT-Overlay: Add shouldForward-interface-method This additional method gives the opportunity to define own conditions when the player should forward, rewind or ignore taps. --- .../vkay94/dtpv/youtube/YouTubeOverlay.kt | 73 +++++++++++++++---- 1 file changed, 60 insertions(+), 13 deletions(-) diff --git a/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/youtube/YouTubeOverlay.kt b/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/youtube/YouTubeOverlay.kt index ff775f9..160ba53 100644 --- a/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/youtube/YouTubeOverlay.kt +++ b/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/youtube/YouTubeOverlay.kt @@ -1,6 +1,7 @@ package com.github.vkay94.dtpv.youtube import android.content.Context +import android.media.session.PlaybackState import android.util.AttributeSet import android.view.LayoutInflater import android.view.View @@ -17,6 +18,7 @@ import com.github.vkay94.dtpv.SeekListener import com.github.vkay94.dtpv.youtube.views.CircleClipTapView import com.github.vkay94.dtpv.youtube.views.SecondsView import com.google.android.exoplayer2.Player +import com.google.android.exoplayer2.ui.PlayerView /** @@ -314,24 +316,25 @@ class YouTubeOverlay(context: Context, private val attrs: AttributeSet?) : val secondsTextView: TextView get() = secondsView.textView + override fun onDoubleTapStarted(posX: Float, posY: Float) { + if (player == null || playerView == null) + return + + if (performListener?.shouldForward(player!!, playerView!!, posX) == null) + return + } + override fun onDoubleTapProgressUp(posX: Float, posY: Float) { // Check first whether forwarding/rewinding is "valid" - if (player?.currentPosition == null || playerView?.width == null) return - player?.currentPosition?.let { current -> - // Rewind and start of the video (+ 0.5 sec tolerance) - if (posX < playerView?.width!! * 0.35 && current <= 500) - return + if (player == null || playerView == null) return - // Forward and end of the video (- 0.5 sec tolerance) - if (posX > playerView?.width!! * 0.65 && current >= player?.duration!!.minus(500)) - return - } + val shouldForward = performListener?.shouldForward(player!!, playerView!!, posX) // YouTube behavior: show overlay on MOTION_UP // But check whether the first double tap is in invalid area if (this.visibility != View.VISIBLE) { - if (posX < playerView?.width!! * 0.35 || posX > playerView?.width!! * 0.65) { + if (shouldForward != null) { performListener?.onAnimationStart() secondsView.visibility = View.VISIBLE secondsView.start() @@ -339,8 +342,8 @@ class YouTubeOverlay(context: Context, private val attrs: AttributeSet?) : return } - when { - posX < playerView?.width!! * 0.35 -> { + when (shouldForward) { + false -> { // First time tap or switched if (secondsView.isForward) { @@ -358,7 +361,7 @@ class YouTubeOverlay(context: Context, private val attrs: AttributeSet?) : } rewinding() } - posX > playerView?.width!! * 0.65 -> { + true -> { // First time tap or switched if (!secondsView.isForward) { @@ -459,5 +462,49 @@ class YouTubeOverlay(context: Context, private val attrs: AttributeSet?) : * Visibility of the overlay should be set to GONE within this interface method. */ fun onAnimationEnd() + + /** + * Determines whether the player should forward, rewind or skip this tap by doing + * nothing / ignoring. Is called for each tap. + * + * By overriding this method you can check for self-defined conditions whether showing the + * overlay and rewinding/forwarding (e.g. if the media source valid) or skip it. + * + * In the following you see the default conditions for each action (if there is no media + * to play ([PlaybackState.STATE_NONE]), an error occurred ([PlaybackState.STATE_ERROR]) + * or the media is stopped ([PlaybackState.STATE_STOPPED]) the tap will be ignored in any + * case): + * + * + * | Action | Current position | Screen width portion | + * |---------|---------------------------|----------------------| + * | rewind | greater than 500 ms | 0% to 35% | + * | forward | less than total duration | 65% to 100% | + * | ignore | ------------ | between 35% and 65% | + * + * @param player Current [Player] + * @param playerView [PlayerView] which accepts the taps + * @param posX Position of the tap on the x-axis + * + * @return `true` to forward, `false` to rewind or `null` to ignore. + */ + fun shouldForward(player: Player, playerView: DoubleTapPlayerView, posX: Float): Boolean? { + + if (player.playbackState == PlaybackState.STATE_ERROR || + player.playbackState == PlaybackState.STATE_NONE || + player.playbackState == PlaybackState.STATE_STOPPED) { + + playerView.cancelInDoubleTapMode() + return null + } + + if (player.currentPosition > 500 && posX < playerView.width * 0.35) + return false + + if (player.currentPosition < player.duration && posX > playerView.width * 0.65) + return true + + return null + } } } From ff3f14c53131bf4b2ce4d6f8ad9a1dfc1aa004b4 Mon Sep 17 00:00:00 2001 From: vkay94 Date: Mon, 29 Mar 2021 19:39:05 +0200 Subject: [PATCH 4/5] Gradle: Update dependencies and bump SDK to 30 --- app/build.gradle | 28 +++++++++++++++------------- build.gradle | 18 ++---------------- dependencies.gradle | 20 ++++++++++++++++++++ doubletapplayerview/build.gradle | 26 ++++++++++++++------------ 4 files changed, 51 insertions(+), 41 deletions(-) create mode 100644 dependencies.gradle diff --git a/app/build.gradle b/app/build.gradle index b3f7162..6849877 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,15 +1,17 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply from: '../dependencies.gradle' + android { - compileSdkVersion 29 - buildToolsVersion '29.0.3' + compileSdkVersion build.compileSdk + buildToolsVersion build.buildTools defaultConfig { applicationId "com.github.vkay94.doubletapplayerviewexample" - minSdkVersion 16 - targetSdkVersion 29 - versionCode libVersionCode - versionName "$libMajor.$libMinor.$libPatch" + minSdkVersion build.minSdk + targetSdkVersion build.targetSdk + versionCode build.libMajor + (build.libMinor * 100) + (build.libPatch * 10000) + versionName "$build.libMajor.$build.libMinor.$build.libPatch" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" multiDexEnabled true } @@ -49,19 +51,19 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "androidx.appcompat:appcompat:$appcompat_version" - implementation "androidx.core:core-ktx:$core_ktx_version" - implementation "androidx.constraintlayout:constraintlayout:$constraintlayout_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlin" + implementation "androidx.appcompat:appcompat:$versions.appcompat" + implementation "androidx.core:core-ktx:$versions.core_ktx" + implementation "androidx.constraintlayout:constraintlayout:$versions.constraintlayout" // ExoPlayer2 - implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version" - implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version" + implementation "com.google.android.exoplayer:exoplayer-core:$versions.exoplayer" + implementation "com.google.android.exoplayer:exoplayer-ui:$versions.exoplayer" implementation "com.github.StephenVinouze:MaterialNumberPicker:1.1.0" implementation project(path: ':doubletapplayerview') - implementation 'com.google.android.material:material:1.2.1' + implementation "com.google.android.material:material:$versions.material" implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.multidex:multidex:2.0.1' } diff --git a/build.gradle b/build.gradle index d09106f..6584b50 100644 --- a/build.gradle +++ b/build.gradle @@ -1,28 +1,14 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext { - libMajor = 1 - libMinor = 0 - libPatch = 0 - - libVersionCode = libPatch + (libMinor * 100) + (libMajor * 10000) - - kotlin_version = '1.4.20' - core_ktx_version = '1.3.2' - exoplayer_version = '2.12.1' - appcompat_version = '1.2.0' - constraintlayout_version = '2.0.4' - } - repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.1' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'com.android.tools.build:gradle:4.1.3' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.31" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } diff --git a/dependencies.gradle b/dependencies.gradle new file mode 100644 index 0000000..8d45ba4 --- /dev/null +++ b/dependencies.gradle @@ -0,0 +1,20 @@ +ext.versions = [ + kotlin : '1.4.31', + core_ktx : '1.3.2', + appcompat : '1.2.0', + constraintlayout : '2.0.4', + material : '1.3.0', + lifecycle : '2.3.0', + exoplayer : '2.12.3' +] + +ext.build = [ + buildTools : '30.0.3', + minSdk : 16, + targetSdk : 30, + compileSdk : 30, + + libMajor : 1, + libMinor : 0, + libPatch : 0, +] \ No newline at end of file diff --git a/doubletapplayerview/build.gradle b/doubletapplayerview/build.gradle index eaa8cba..893fe24 100644 --- a/doubletapplayerview/build.gradle +++ b/doubletapplayerview/build.gradle @@ -1,15 +1,17 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' +apply from: '../dependencies.gradle' + android { - compileSdkVersion 29 - buildToolsVersion '29.0.3' + compileSdkVersion build.compileSdk + buildToolsVersion build.buildTools defaultConfig { - minSdkVersion 16 - targetSdkVersion 29 - versionCode libVersionCode - versionName "$libMajor.$libMinor.$libPatch" + minSdkVersion build.minSdk + targetSdkVersion build.targetSdk + versionCode build.libMajor + (build.libMinor * 100) + (build.libPatch * 10000) + versionName "$build.libMajor.$build.libMinor.$build.libPatch" vectorDrawables.useSupportLibrary = true @@ -36,11 +38,11 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "androidx.appcompat:appcompat:$appcompat_version" - implementation "androidx.core:core-ktx:$core_ktx_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlin" + implementation "androidx.appcompat:appcompat:$versions.appcompat" + implementation "androidx.core:core-ktx:$versions.core_ktx" - implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version" - implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version" - implementation "androidx.constraintlayout:constraintlayout:$constraintlayout_version" + implementation "com.google.android.exoplayer:exoplayer-core:$versions.exoplayer" + implementation "com.google.android.exoplayer:exoplayer-ui:$versions.exoplayer" + implementation "androidx.constraintlayout:constraintlayout:$versions.constraintlayout" } From 50af2019a7aebeee1f3107968a792d64e8bcb65b Mon Sep 17 00:00:00 2001 From: vkay94 Date: Mon, 29 Mar 2021 19:42:42 +0200 Subject: [PATCH 5/5] Release 1.0.1 --- README.md | 19 ++++++++++++------- dependencies.gradle | 2 +- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 956ed14..6d5b45f 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,8 @@ allprojects { } ``` -Then, in your app's directory, you can include it the same way like other libraries. Make sure that you set the comnpatibilty version to 1.8 inside `compileOptions`: +Then, in your app's directory, you can include it the same way like other libraries. +Make sure that you set the comnpatibilty version to 1.8 inside `compileOptions`: ```gradle android { @@ -41,11 +42,12 @@ android { } dependencies { - implementation 'com.github.vkay94:DoubleTapPlayerView:1.0.0' + implementation 'com.github.vkay94:DoubleTapPlayerView:1.0.1' } ``` -The minimum API level supported by this library is API 16 as ExoPlayer does, but I can't verify versions below API level 21 (Lollipop) myself. So feedback is welcomed. +The minimum API level supported by this library is API 16 as ExoPlayer does, but I can't +verify versions below API level 21 (Lollipop) myself. So feedback is welcomed. # Getting started @@ -78,7 +80,8 @@ into your XML layout, e.g. on top of `DoubleTapPlayerView` or inside ExoPlayer's ``` Then, inside your `Activity` or `Fragment`, you can specify which preparations should be done -before and after the animation, but at least, you have got to toggle the visibility of the overlay and reference the (Simple)ExoPlayer to it: +before and after the animation, but at least, you have got to toggle the visibility of the +overlay and reference the (Simple)ExoPlayer to it: ```kotlin youtube_overlay @@ -103,8 +106,9 @@ youtube_overlay youtube_overlay.player(simpleExoPlayer) ``` -This way, you have more control about the appearance, for example you could apply a fading animation to it. -For a full initialization you can refer to the demo application's MainActivity and layout files. +This way, you have more control about the appearance, for example you could apply a fading +animation to it. For a full initialization you can refer to the demo application's MainActivity +and layout files. --- @@ -116,7 +120,8 @@ The following sections provide detailed documentation for the components of the `DoubleTapPlayerView` is the core of this library. It recognizes specific gestures which provides more control for the double tapping gesture. -An overview about the added methods can be found in the [PlayerDoubleTapListener][PlayerDoubleTapListener] interface. +An overview about the added methods can be found in the [PlayerDoubleTapListener][PlayerDoubleTapListener] +interface. You can adjust how long the double tap mode remains after the last action, the default value is 650 milliseconds. diff --git a/dependencies.gradle b/dependencies.gradle index 8d45ba4..8e6261f 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -16,5 +16,5 @@ ext.build = [ libMajor : 1, libMinor : 0, - libPatch : 0, + libPatch : 1, ] \ No newline at end of file