From 90711c5d782ec16ecaf8477d374634bbef385449 Mon Sep 17 00:00:00 2001 From: tejpratapsingh Date: Fri, 3 Jul 2026 23:48:18 +0530 Subject: [PATCH] chore: update ktlint.yml --- .editorconfig | 2 + .github/workflows/ktlint.yml | 6 + .idea/deploymentTargetSelector.xml | 11 + .../animator/ui/view/RenaultCar.kt | 4 +- .../motionlib/core/MotionLayoutInfo.kt | 6 +- .../motionlib/core/VideoAspectRatio.kt | 1 + .../core/MotionTextSizeProviderTest.kt | 15 +- modules/ivi-demo/build.gradle | 4 +- .../ExampleInstrumentedTest.kt | 2 +- .../{ivi_demo => ividemo}/MainActivity.kt | 11 +- .../extension/ViewExtensions.kt | 2 +- .../motion/RenaultCar.kt | 14 +- .../{ivi_demo => ividemo}/motion/Road.kt | 6 +- .../sequence/RenaultSequence.kt | 6 +- .../view/TrapezoidImageView.kt | 2 +- .../{ivi_demo => ividemo}/ExampleUnitTest.kt | 2 +- .../compose/ProjectDetailsScreenTest.kt | 49 +++- .../worker/LyricsMotionWorkerTest.kt | 3 +- .../compose/common/GradientText.kt | 1 + .../compose/common/GradientTextPreview.kt | 1 + .../compose/details/ProjectDetailsScreen.kt | 1 + .../presentation/compose/lyrics/DragHandle.kt | 1 + .../presentation/compose/lyrics/LyricRow.kt | 1 + .../compose/lyrics/StartDragHandle.kt | 1 + .../compose/lyrics/SyncedLyricsSelector.kt | 20 +- .../compose/navigation/AppNavHost.kt | 1 + .../compose/projects/CreateNewProjectCard.kt | 1 + .../projects/DeleteConfirmationDialog.kt | 1 + .../compose/projects/ProjectCard.kt | 1 + .../projects/ProjectThumbnailExtractor.kt | 1 - .../compose/projects/ProjectsRoute.kt | 1 + .../compose/projects/ProjectsScreen.kt | 1 + .../compose/projects/ThumbnailCache.kt | 16 +- .../compose/search/SearchScreen.kt | 259 +++++++----------- .../templates/LyricsTemplateSelector.kt | 3 +- .../compose/templates/TemplatePreviewItem.kt | 3 +- .../templates/ZoomLyricsTemplate.kt | 1 - .../presentation/ui/theme/Theme.kt | 1 + .../presentation/viewmodel/LyricsViewModel.kt | 11 +- .../LyricsMotionWorkerCancelReceiver.kt | 5 +- .../presentation/compose/ScreenTest.kt | 3 +- .../viewmodel/LyricsViewModelTest.kt | 55 ++-- .../src/main/AndroidManifest.xml | 2 +- .../motionlib/media3/FrameProcessor.kt | 3 +- .../media3/Media3CompositionBuilder.kt | 13 +- .../media3/Media3TransformerRunner.kt | 1 - .../media3/Media3VideoProducerAdapter.kt | 14 +- .../plugins/SubjectSegmentationPlugin.kt | 25 +- .../motionstore/dao/MotionProjectDao.kt | 5 +- .../motionstore/infra/PreferenceManager.kt | 4 +- .../motioneditor/TimelineData.kt | 4 +- .../motioneditor/ui/MotionEditorScreen.kt | 10 +- .../motioneditor/ui/MotionTimeline.kt | 24 +- .../motioneditor/utils/TimelineUtils.kt | 24 +- .../custom/video/MotionVideoPlayerCompose.kt | 1 + .../motionlib/core/animation/Spring.kt | 6 +- .../core/motion/MotionVideoProducer.kt | 42 +-- .../text/abstract/AbstractMotionTextView.kt | 1 - .../motionlib/ui/effects/BlurEffect.kt | 8 +- .../ui/effects/BrightnessContrastEffect.kt | 59 ++-- .../motionlib/ui/effects/ChainEffect.kt | 6 +- .../motionlib/ui/effects/FadeInEffect.kt | 4 +- .../motionlib/ui/effects/FadeOutEffect.kt | 4 +- .../motionlib/ui/effects/GrayscaleEffect.kt | 24 +- .../motionlib/ui/effects/InvertEffect.kt | 77 ++++-- .../motionlib/ui/effects/OffsetEffect.kt | 28 +- .../motionlib/ui/effects/PixelateEffect.kt | 22 +- .../motionlib/ui/effects/SepiaEffect.kt | 78 ++++-- .../motionlib/ui/effects/VibrateEffect.kt | 2 +- .../motionlib/ui/effects/VintageEffect.kt | 48 +++- .../motionlib/utils/ImageUtil.kt | 52 ++-- .../motionlib/worker/MotionWorker.kt | 2 +- .../core/motion/MotionTransitionTest.kt | 21 +- .../motion/sdui/infra/MotionSDUIParsers.kt | 5 +- .../motion/sdui/infra/MotionSdui.kt | 93 +++++-- .../sdui/infra/MotionTransitionParser.kt | 8 +- .../sdui/infra/VideoAspectRatioParser.kt | 4 +- .../MotionTemplateInstrumentationTest.kt | 118 ++++---- .../sdui/MotionTemplateSDUIProviderTest.kt | 52 ++-- .../templates/json/JsonMotionTemplate.kt | 5 +- .../templates/model/MotionTemplate.kt | 2 +- .../motionlib/templates/model/TemplateData.kt | 7 +- .../templates/model/TemplateParameter.kt | 10 +- .../templates/TemplateSerializationTest.kt | 92 ++++--- 84 files changed, 911 insertions(+), 635 deletions(-) create mode 100644 .editorconfig rename modules/ivi-demo/src/androidTest/java/com/tejpratapsingh/{ivi_demo => ividemo}/ExampleInstrumentedTest.kt (94%) rename modules/ivi-demo/src/main/java/com/tejpratapsingh/{ivi_demo => ividemo}/MainActivity.kt (85%) rename modules/ivi-demo/src/main/java/com/tejpratapsingh/{ivi_demo => ividemo}/extension/ViewExtensions.kt (97%) rename modules/ivi-demo/src/main/java/com/tejpratapsingh/{ivi_demo => ividemo}/motion/RenaultCar.kt (92%) rename modules/ivi-demo/src/main/java/com/tejpratapsingh/{ivi_demo => ividemo}/motion/Road.kt (92%) rename modules/ivi-demo/src/main/java/com/tejpratapsingh/{ivi_demo => ividemo}/sequence/RenaultSequence.kt (87%) rename modules/ivi-demo/src/main/java/com/tejpratapsingh/{ivi_demo => ividemo}/view/TrapezoidImageView.kt (97%) rename modules/ivi-demo/src/test/java/com/tejpratapsingh/{ivi_demo => ividemo}/ExampleUnitTest.kt (90%) diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..f4923b02 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,2 @@ +[*.{kt,kts}] +ktlint_standard_function-naming = disabled diff --git a/.github/workflows/ktlint.yml b/.github/workflows/ktlint.yml index 091d0704..29e28e1c 100644 --- a/.github/workflows/ktlint.yml +++ b/.github/workflows/ktlint.yml @@ -27,3 +27,9 @@ jobs: report-path: build/*.xml # Support glob patterns by https://www.npmjs.com/package/@actions/glob ignore-warnings: true # Ignore Lint Warnings continue-on-error: false # If annotations contain error of severity, action-ktlint exit 1. + - name: Upload ktlint report + uses: actions/upload-artifact@v4 + if: always() + with: + name: ktlint-report + path: build/ktlint-report.xml diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index 8cc5d7c5..86cca123 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -27,6 +27,17 @@ + + \ No newline at end of file diff --git a/modules/app/src/main/java/com/tejpratapsingh/animator/ui/view/RenaultCar.kt b/modules/app/src/main/java/com/tejpratapsingh/animator/ui/view/RenaultCar.kt index 7649c891..184668d7 100644 --- a/modules/app/src/main/java/com/tejpratapsingh/animator/ui/view/RenaultCar.kt +++ b/modules/app/src/main/java/com/tejpratapsingh/animator/ui/view/RenaultCar.kt @@ -20,7 +20,7 @@ class RenaultCar( effects: List = emptyList(), ) : BaseContourMotionView(context, startFrame, endFrame, effects = effects) { companion object { - const val imageAssetSubFolder = "renault_kiger_bg" + const val IMAGE_ASSET_SUB_FOLDER = "renault_kiger_bg" } private val imageView: ImageView = @@ -78,7 +78,7 @@ class RenaultCar( ) // Determine which image to show based on the current frame - val imageName = "$imageAssetSubFolder/$frame.png" + val imageName = "$IMAGE_ASSET_SUB_FOLDER/$frame.png" try { val inputStream: InputStream = assetManager.open(imageName) diff --git a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/MotionLayoutInfo.kt b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/MotionLayoutInfo.kt index 064f15d7..3a8a9518 100644 --- a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/MotionLayoutInfo.kt +++ b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/MotionLayoutInfo.kt @@ -12,20 +12,20 @@ data class MotionLayoutInfo( val height: Int = WRAP_CONTENT, val padding: Padding = Padding(), val margin: Margin = Margin(), - val gravity: Int = Gravity.NO_GRAVITY + val gravity: Int = Gravity.NO_GRAVITY, ) { data class Padding( val left: Int = 0, val top: Int = 0, val right: Int = 0, - val bottom: Int = 0 + val bottom: Int = 0, ) data class Margin( val left: Int = 0, val top: Int = 0, val right: Int = 0, - val bottom: Int = 0 + val bottom: Int = 0, ) companion object { diff --git a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/VideoAspectRatio.kt b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/VideoAspectRatio.kt index cd031136..a8e7ef35 100644 --- a/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/VideoAspectRatio.kt +++ b/modules/core/src/main/java/com/tejpratapsingh/motionlib/core/VideoAspectRatio.kt @@ -1,5 +1,6 @@ package com.tejpratapsingh.motionlib.core +@Suppress("ClassName") sealed class VideoAspectRatio( val width: Int, val height: Int, diff --git a/modules/core/src/test/java/com/tejpratapsingh/motionlib/core/MotionTextSizeProviderTest.kt b/modules/core/src/test/java/com/tejpratapsingh/motionlib/core/MotionTextSizeProviderTest.kt index 3d963468..303000a3 100644 --- a/modules/core/src/test/java/com/tejpratapsingh/motionlib/core/MotionTextSizeProviderTest.kt +++ b/modules/core/src/test/java/com/tejpratapsingh/motionlib/core/MotionTextSizeProviderTest.kt @@ -5,7 +5,6 @@ import org.junit.Before import org.junit.Test class MotionTextSizeProviderTest { - @Before fun setup() { // Reset baseTextScale before each test @@ -15,10 +14,10 @@ class MotionTextSizeProviderTest { @Test fun testDefaultFontSizes() { val aspectRatio = VideoAspectRatio.Ratio16x9_1080 // 1920x1080, min is 1080, scale is 1.0 - + // H1 should be 160f * 1.0 = 160f assertEquals(160f, MotionTextSizeProvider.getFontSize(aspectRatio, MotionTextVariant.H1), 0.01f) - + // P should be 48f * 1.0 = 48f assertEquals(48f, MotionTextSizeProvider.getFontSize(aspectRatio, MotionTextVariant.P), 0.01f) } @@ -26,22 +25,22 @@ class MotionTextSizeProviderTest { @Test fun testBaseTextScale() { val aspectRatio = VideoAspectRatio.Ratio16x9_1080 // 1920x1080, min is 1080, scale is 1.0 - + MotionTextSizeProvider.baseTextScale = 2.0f - + // H1 should be 160f * 2.0 = 320f assertEquals(320f, MotionTextSizeProvider.getFontSize(aspectRatio, MotionTextVariant.H1), 0.01f) - + // P should be 48f * 2.0 = 96f assertEquals(96f, MotionTextSizeProvider.getFontSize(aspectRatio, MotionTextVariant.P), 0.01f) } @Test fun testScalingWithAspectRatio() { - // 480p 16:9 is 854x480, min is 480. + // 480p 16:9 is 854x480, min is 480. // Scale = 480 / 1080 = 0.4444... val aspectRatio = VideoAspectRatio.Ratio16x9_480 - + val expectedH1 = 160f * (480f / 1080f) assertEquals(expectedH1, MotionTextSizeProvider.getFontSize(aspectRatio, MotionTextVariant.H1), 0.01f) } diff --git a/modules/ivi-demo/build.gradle b/modules/ivi-demo/build.gradle index f4c316ee..aeac1f7b 100644 --- a/modules/ivi-demo/build.gradle +++ b/modules/ivi-demo/build.gradle @@ -3,11 +3,11 @@ plugins { } android { - namespace 'com.tejpratapsingh.ivi_demo' + namespace 'com.tejpratapsingh.ividemo' compileSdk 36 defaultConfig { - applicationId "com.tejpratapsingh.ivi_demo" + applicationId "com.tejpratapsingh.ividemo" minSdk 28 targetSdk 36 versionCode 1 diff --git a/modules/ivi-demo/src/androidTest/java/com/tejpratapsingh/ivi_demo/ExampleInstrumentedTest.kt b/modules/ivi-demo/src/androidTest/java/com/tejpratapsingh/ividemo/ExampleInstrumentedTest.kt similarity index 94% rename from modules/ivi-demo/src/androidTest/java/com/tejpratapsingh/ivi_demo/ExampleInstrumentedTest.kt rename to modules/ivi-demo/src/androidTest/java/com/tejpratapsingh/ividemo/ExampleInstrumentedTest.kt index 3333ff22..dfb122fc 100644 --- a/modules/ivi-demo/src/androidTest/java/com/tejpratapsingh/ivi_demo/ExampleInstrumentedTest.kt +++ b/modules/ivi-demo/src/androidTest/java/com/tejpratapsingh/ividemo/ExampleInstrumentedTest.kt @@ -1,4 +1,4 @@ -package com.tejpratapsingh.ivi_demo +package com.tejpratapsingh.ividemo import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry diff --git a/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/MainActivity.kt b/modules/ivi-demo/src/main/java/com/tejpratapsingh/ividemo/MainActivity.kt similarity index 85% rename from modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/MainActivity.kt rename to modules/ivi-demo/src/main/java/com/tejpratapsingh/ividemo/MainActivity.kt index 72bce271..fe325442 100644 --- a/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/MainActivity.kt +++ b/modules/ivi-demo/src/main/java/com/tejpratapsingh/ividemo/MainActivity.kt @@ -1,9 +1,8 @@ -package com.tejpratapsingh.ivi_demo +package com.tejpratapsingh.ividemo -import RenaultCar -import android.os.Build import android.os.Bundle -import com.tejpratapsingh.ivi_demo.extension.enableSwipeSeekReverse +import com.tejpratapsingh.ividemo.extension.enableSwipeSeekReverse +import com.tejpratapsingh.ividemo.motion.RenaultCar import com.tejpratapsingh.motionlib.activities.PreviewActivity import com.tejpratapsingh.motionlib.core.MotionConfig import com.tejpratapsingh.motionlib.core.VideoAspectRatio @@ -39,9 +38,7 @@ class MainActivity : PreviewActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - motionVideoPlayer.seekBar.min = 1 - } + motionVideoPlayer.seekBar.min = 1 motionView.enableSwipeSeekReverse( maxProgress = video.totalFrames, diff --git a/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/extension/ViewExtensions.kt b/modules/ivi-demo/src/main/java/com/tejpratapsingh/ividemo/extension/ViewExtensions.kt similarity index 97% rename from modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/extension/ViewExtensions.kt rename to modules/ivi-demo/src/main/java/com/tejpratapsingh/ividemo/extension/ViewExtensions.kt index 5b88c362..99cd3159 100644 --- a/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/extension/ViewExtensions.kt +++ b/modules/ivi-demo/src/main/java/com/tejpratapsingh/ividemo/extension/ViewExtensions.kt @@ -1,4 +1,4 @@ -package com.tejpratapsingh.ivi_demo.extension +package com.tejpratapsingh.ividemo.extension import android.annotation.SuppressLint import android.view.MotionEvent diff --git a/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/motion/RenaultCar.kt b/modules/ivi-demo/src/main/java/com/tejpratapsingh/ividemo/motion/RenaultCar.kt similarity index 92% rename from modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/motion/RenaultCar.kt rename to modules/ivi-demo/src/main/java/com/tejpratapsingh/ividemo/motion/RenaultCar.kt index 7b752821..eb95ff78 100644 --- a/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/motion/RenaultCar.kt +++ b/modules/ivi-demo/src/main/java/com/tejpratapsingh/ividemo/motion/RenaultCar.kt @@ -1,3 +1,5 @@ +package com.tejpratapsingh.ividemo.motion + import android.content.Context import android.graphics.BitmapFactory import android.widget.ImageView @@ -22,8 +24,8 @@ class RenaultCar( effects: List = emptyList(), ) : BaseContourMotionView(context, startFrame, endFrame, effects = effects) { companion object { - const val imageAssetSubFolder = "renault_kiger_bg" - const val roadAssetSubFolder = "road" + const val IMAGE_ASSET_SUB_FOLDER = "renault_kiger_bg" + const val ROAD_ASSET_SUB_FOLDER = "road" } private val imageViewBg: ImageView = @@ -37,8 +39,8 @@ class RenaultCar( } private val assetManager = context.assets - private val files = assetManager.list(imageAssetSubFolder) - private val roadFiles = assetManager.list(roadAssetSubFolder) + private val files = assetManager.list(IMAGE_ASSET_SUB_FOLDER) + private val roadFiles = assetManager.list(ROAD_ASSET_SUB_FOLDER) init { imageViewBg.layoutBy( @@ -120,7 +122,7 @@ class RenaultCar( // val road = String.format( // Locale.getDefault(), // "%s/%02d.png", -// roadAssetSubFolder, +// ROAD_ASSET_SUB_FOLDER, // min(roadInterpolator, (roadFiles?.size ?: 1) - 1) // ) // @@ -137,7 +139,7 @@ class RenaultCar( String.format( Locale.getDefault(), "%s/%d.png", - imageAssetSubFolder, + IMAGE_ASSET_SUB_FOLDER, min(frame, (files?.size ?: 1) - 1), ) diff --git a/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/motion/Road.kt b/modules/ivi-demo/src/main/java/com/tejpratapsingh/ividemo/motion/Road.kt similarity index 92% rename from modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/motion/Road.kt rename to modules/ivi-demo/src/main/java/com/tejpratapsingh/ividemo/motion/Road.kt index 95a8126a..dbd0b36c 100644 --- a/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/motion/Road.kt +++ b/modules/ivi-demo/src/main/java/com/tejpratapsingh/ividemo/motion/Road.kt @@ -1,4 +1,4 @@ -package com.tejpratapsingh.ivi_demo.motion +package com.tejpratapsingh.ividemo.motion import android.content.Context import android.graphics.BitmapFactory @@ -18,7 +18,7 @@ class Road( effects: List = emptyList(), ) : BaseContourMotionView(context, startFrame, endFrame, effects = effects) { companion object { - const val imageAssetSubFolder = "road" + const val IMAGE_ASSET_SUB_FOLDER = "road" } private val imageView: ImageView = @@ -60,7 +60,7 @@ class Road( super.forFrame(frame) // Determine which image to show based on the current frame - val imageName = "$imageAssetSubFolder/$frame.png" + val imageName = "$IMAGE_ASSET_SUB_FOLDER/$frame.png" try { val inputStream: InputStream = assetManager.open(imageName) diff --git a/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/sequence/RenaultSequence.kt b/modules/ivi-demo/src/main/java/com/tejpratapsingh/ividemo/sequence/RenaultSequence.kt similarity index 87% rename from modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/sequence/RenaultSequence.kt rename to modules/ivi-demo/src/main/java/com/tejpratapsingh/ividemo/sequence/RenaultSequence.kt index 1b506627..27e84cb6 100644 --- a/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/sequence/RenaultSequence.kt +++ b/modules/ivi-demo/src/main/java/com/tejpratapsingh/ividemo/sequence/RenaultSequence.kt @@ -1,7 +1,7 @@ -package com.tejpratapsingh.ivi_demo.sequence +package com.tejpratapsingh.ividemo.sequence -import RenaultCar import android.content.Context +import com.tejpratapsingh.ividemo.motion.RenaultCar import com.tejpratapsingh.motionlib.core.MotionConfig import com.tejpratapsingh.motionlib.core.VideoAspectRatio import com.tejpratapsingh.motionlib.core.motion.BaseContourMotionView @@ -18,7 +18,7 @@ fun sampleMotionVideo(applicationContext: Context): MotionVideoProducer { setCurrentConfig(motionConfig) val assetManager = applicationContext.assets - val files = assetManager.list(RenaultCar.imageAssetSubFolder) + val files = assetManager.list(RenaultCar.IMAGE_ASSET_SUB_FOLDER) val motionView: BaseContourMotionView = RenaultCar( diff --git a/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/view/TrapezoidImageView.kt b/modules/ivi-demo/src/main/java/com/tejpratapsingh/ividemo/view/TrapezoidImageView.kt similarity index 97% rename from modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/view/TrapezoidImageView.kt rename to modules/ivi-demo/src/main/java/com/tejpratapsingh/ividemo/view/TrapezoidImageView.kt index 7298f227..aee0db11 100644 --- a/modules/ivi-demo/src/main/java/com/tejpratapsingh/ivi_demo/view/TrapezoidImageView.kt +++ b/modules/ivi-demo/src/main/java/com/tejpratapsingh/ividemo/view/TrapezoidImageView.kt @@ -1,4 +1,4 @@ -package com.tejpratapsingh.ivi_demo.view +package com.tejpratapsingh.ividemo.view import android.content.Context import android.graphics.Bitmap diff --git a/modules/ivi-demo/src/test/java/com/tejpratapsingh/ivi_demo/ExampleUnitTest.kt b/modules/ivi-demo/src/test/java/com/tejpratapsingh/ividemo/ExampleUnitTest.kt similarity index 90% rename from modules/ivi-demo/src/test/java/com/tejpratapsingh/ivi_demo/ExampleUnitTest.kt rename to modules/ivi-demo/src/test/java/com/tejpratapsingh/ividemo/ExampleUnitTest.kt index a3032286..3e79791a 100644 --- a/modules/ivi-demo/src/test/java/com/tejpratapsingh/ivi_demo/ExampleUnitTest.kt +++ b/modules/ivi-demo/src/test/java/com/tejpratapsingh/ividemo/ExampleUnitTest.kt @@ -1,4 +1,4 @@ -package com.tejpratapsingh.ivi_demo +package com.tejpratapsingh.ividemo import org.junit.Assert.assertEquals import org.junit.Test diff --git a/modules/lyrics-maker/src/androidTest/java/com/tejpratapsingh/lyricsmaker/presentation/compose/ProjectDetailsScreenTest.kt b/modules/lyrics-maker/src/androidTest/java/com/tejpratapsingh/lyricsmaker/presentation/compose/ProjectDetailsScreenTest.kt index 74d9166b..bfa01bd8 100644 --- a/modules/lyrics-maker/src/androidTest/java/com/tejpratapsingh/lyricsmaker/presentation/compose/ProjectDetailsScreenTest.kt +++ b/modules/lyrics-maker/src/androidTest/java/com/tejpratapsingh/lyricsmaker/presentation/compose/ProjectDetailsScreenTest.kt @@ -28,7 +28,6 @@ import org.junit.runner.RunWith */ @RunWith(AndroidJUnit4::class) class ProjectDetailsScreenTest { - @get:Rule val composeTestRule = createComposeRule() @@ -72,6 +71,7 @@ class ProjectDetailsScreenTest { ProjectDetailsScreen( project = project, onBackClick = {}, + onEditClick = {}, onShareClick = {}, ) } @@ -91,6 +91,7 @@ class ProjectDetailsScreenTest { ProjectDetailsScreen( project = project, onBackClick = {}, + onEditClick = {}, onShareClick = {}, ) } @@ -113,6 +114,7 @@ class ProjectDetailsScreenTest { ProjectDetailsScreen( project = project, onBackClick = {}, + onEditClick = {}, onShareClick = {}, ) } @@ -133,6 +135,7 @@ class ProjectDetailsScreenTest { ProjectDetailsScreen( project = project, onBackClick = {}, + onEditClick = {}, onShareClick = {}, ) } @@ -149,6 +152,7 @@ class ProjectDetailsScreenTest { ProjectDetailsScreen( project = project, onBackClick = {}, + onEditClick = {}, onShareClick = { sharedProject = it }, ) } @@ -170,6 +174,7 @@ class ProjectDetailsScreenTest { ProjectDetailsScreen( project = project, onBackClick = {}, + onEditClick = {}, onShareClick = {}, ) } @@ -186,6 +191,7 @@ class ProjectDetailsScreenTest { ProjectDetailsScreen( project = project, onBackClick = { backClicked = true }, + onEditClick = {}, onShareClick = {}, ) } @@ -194,4 +200,43 @@ class ProjectDetailsScreenTest { assertEquals(true, backClicked) } -} \ No newline at end of file + + // ------------------------------------------------------------------ + // Edit button + // ------------------------------------------------------------------ + + @Test + fun editButton_isDisplayed() { + val project = buildProject() + + composeTestRule.setContent { + ProjectDetailsScreen( + project = project, + onBackClick = {}, + onEditClick = {}, + onShareClick = {}, + ) + } + + composeTestRule.onNodeWithText("Edit Project").assertIsDisplayed() + } + + @Test + fun editButton_invokesOnEditClickWithProject() { + val project = buildProject(id = "edit-test") + var editedProject: MotionProject? = null + + composeTestRule.setContent { + ProjectDetailsScreen( + project = project, + onBackClick = {}, + onEditClick = { editedProject = it }, + onShareClick = {}, + ) + } + + composeTestRule.onNodeWithText("Edit Project").performClick() + + assertEquals(project.id, editedProject?.id) + } +} diff --git a/modules/lyrics-maker/src/androidTest/java/com/tejpratapsingh/lyricsmaker/presentation/worker/LyricsMotionWorkerTest.kt b/modules/lyrics-maker/src/androidTest/java/com/tejpratapsingh/lyricsmaker/presentation/worker/LyricsMotionWorkerTest.kt index 17e04c98..3de95a5f 100644 --- a/modules/lyrics-maker/src/androidTest/java/com/tejpratapsingh/lyricsmaker/presentation/worker/LyricsMotionWorkerTest.kt +++ b/modules/lyrics-maker/src/androidTest/java/com/tejpratapsingh/lyricsmaker/presentation/worker/LyricsMotionWorkerTest.kt @@ -24,7 +24,6 @@ import java.util.UUID */ @RunWith(AndroidJUnit4::class) class LyricsMotionWorkerTest { - private val context: Context = ApplicationProvider.getApplicationContext() private val songName = "Worker Test Song" private val projectId = songName.md5() @@ -161,4 +160,4 @@ class LyricsMotionWorkerTest { WorkManager.getInstance(context).cancelWorkById(workId1) WorkManager.getInstance(context).cancelWorkById(workId2) } -} \ No newline at end of file +} diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/common/GradientText.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/common/GradientText.kt index c9b6e502..42cbd30e 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/common/GradientText.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/common/GradientText.kt @@ -26,6 +26,7 @@ import com.tejpratapsingh.lyricsmaker.presentation.ui.theme.ThemeBlue import com.tejpratapsingh.lyricsmaker.presentation.ui.theme.ThemePink @Composable +@Suppress("FunctionName") fun GradientText( text: String, modifier: Modifier = Modifier, diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/common/GradientTextPreview.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/common/GradientTextPreview.kt index ec2d1983..fd046b6c 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/common/GradientTextPreview.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/common/GradientTextPreview.kt @@ -5,6 +5,7 @@ import androidx.compose.ui.tooling.preview.Preview @Preview(showBackground = true) @Composable +@Suppress("ktlint:standard:function-naming") fun GradientTextPreview() { GradientText(text = "Lyrics Maker") } diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/details/ProjectDetailsScreen.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/details/ProjectDetailsScreen.kt index aa91ddd9..9bd06376 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/details/ProjectDetailsScreen.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/details/ProjectDetailsScreen.kt @@ -48,6 +48,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @Composable +@Suppress("ktlint:standard:function-naming") fun ProjectDetailsScreen( project: MotionProject, onBackClick: () -> Unit, diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/lyrics/DragHandle.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/lyrics/DragHandle.kt index 1d4a5147..811ff0f4 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/lyrics/DragHandle.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/lyrics/DragHandle.kt @@ -35,6 +35,7 @@ import androidx.compose.ui.zIndex import kotlin.math.roundToInt @Composable +@Suppress("FunctionName") internal fun DragHandle( label: String, color: Color, diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/lyrics/LyricRow.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/lyrics/LyricRow.kt index 342f5683..6a0212ec 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/lyrics/LyricRow.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/lyrics/LyricRow.kt @@ -20,6 +20,7 @@ import com.tejpratapsingh.lyricsmaker.data.lrc.SyncedLyricFrame import com.tejpratapsingh.motionlib.core.provideCurrentConfig @Composable +@Suppress("FunctionName") internal fun LyricRow( line: SyncedLyricFrame, isSelected: Boolean, diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/lyrics/StartDragHandle.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/lyrics/StartDragHandle.kt index 93fbecdb..3421a4e7 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/lyrics/StartDragHandle.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/lyrics/StartDragHandle.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.zIndex import kotlin.math.roundToInt @Composable +@Suppress("FunctionName") internal fun StartDragHandle( color: Color, autoScroll: AutoScrollState, diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/lyrics/SyncedLyricsSelector.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/lyrics/SyncedLyricsSelector.kt index feeeab24..e349cc99 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/lyrics/SyncedLyricsSelector.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/lyrics/SyncedLyricsSelector.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed @@ -19,7 +18,6 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Clear -import androidx.compose.material.icons.filled.DragHandle import androidx.compose.material.icons.filled.NavigateNext import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.HorizontalDivider @@ -53,12 +51,13 @@ import com.tejpratapsingh.lyricsmaker.presentation.ui.theme.ThemeBlue import com.tejpratapsingh.lyricsmaker.presentation.ui.theme.ThemePink import com.tejpratapsingh.lyricsmaker.presentation.viewmodel.LyricsViewModel import com.tejpratapsingh.motionlib.core.provideCurrentConfig -import kotlin.math.max -import kotlin.math.min import kotlinx.coroutines.delay import kotlinx.coroutines.isActive +import kotlin.math.max +import kotlin.math.min @Composable +@Suppress("FunctionName") fun SyncedLyricsSelector( viewModel: LyricsViewModel, modifier: Modifier = Modifier, @@ -112,7 +111,8 @@ fun SyncedLyricsSelector( when (activeHandle) { ListItem.StartHandle -> { - val found = findLyricIndexAt(listState, autoScroll.listTopInRoot, livePointerYInRoot) + val found = + findLyricIndexAt(listState, autoScroll.listTopInRoot, livePointerYInRoot) if (found != null) { if (moveMode) { val newStart = found.coerceIn(0, lastIdx - rangeSize) @@ -127,7 +127,8 @@ fun SyncedLyricsSelector( } ListItem.EndHandle -> { - val found = findLyricIndexAt(listState, autoScroll.listTopInRoot, livePointerYInRoot) + val found = + findLyricIndexAt(listState, autoScroll.listTopInRoot, livePointerYInRoot) if (found != null) { if (moveMode) { val newEnd = found.coerceIn(rangeSize, lastIdx) @@ -153,7 +154,10 @@ fun SyncedLyricsSelector( if (lyrics.isEmpty()) { emptyList() } else { - lyrics.subList(selection.minIndex, (selection.maxIndex + 1).coerceAtMost(lyrics.size)) + lyrics.subList( + selection.minIndex, + (selection.maxIndex + 1).coerceAtMost(lyrics.size), + ) } } } @@ -202,7 +206,7 @@ fun SyncedLyricsSelector( if (lyrics.isNotEmpty()) { Surface( tonalElevation = 2.dp, - modifier = Modifier.statusBarsPadding() + modifier = Modifier.statusBarsPadding(), ) { Row( modifier = diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/navigation/AppNavHost.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/navigation/AppNavHost.kt index 34530504..9ba6a89d 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/navigation/AppNavHost.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/navigation/AppNavHost.kt @@ -26,6 +26,7 @@ import com.tejpratapsingh.motionlib.templates.sdui.MotionTemplateSDUIProvider import com.tejpratapsingh.motionstore.tables.MotionProject @Composable +@Suppress("ktlint:standard:function-naming") fun AppNavHost( navController: NavHostController, currentScreen: Screen = Screen.Projects, diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/CreateNewProjectCard.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/CreateNewProjectCard.kt index 628b4ad2..d382349c 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/CreateNewProjectCard.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/CreateNewProjectCard.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp @Composable +@Suppress("ktlint:standard:function-naming") internal fun CreateNewProjectCard( onClick: () -> Unit, modifier: Modifier = Modifier, diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/DeleteConfirmationDialog.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/DeleteConfirmationDialog.kt index 66d3b416..e2c0495d 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/DeleteConfirmationDialog.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/DeleteConfirmationDialog.kt @@ -12,6 +12,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable @Composable +@Suppress("ktlint:standard:function-naming") internal fun DeleteConfirmationDialog( projectName: String, onConfirm: () -> Unit, diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/ProjectCard.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/ProjectCard.kt index fff37641..6ce95454 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/ProjectCard.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/ProjectCard.kt @@ -47,6 +47,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @Composable +@Suppress("ktlint:standard:function-naming") internal fun ProjectCard( project: MotionProject, onClick: () -> Unit, diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/ProjectThumbnailExtractor.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/ProjectThumbnailExtractor.kt index acf2db91..9f09cb6d 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/ProjectThumbnailExtractor.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/ProjectThumbnailExtractor.kt @@ -2,7 +2,6 @@ package com.tejpratapsingh.lyricsmaker.presentation.compose.projects import android.graphics.Bitmap import android.media.MediaMetadataRetriever -import java.lang.Exception fun extractFirstFrame(videoPath: String): Bitmap? { val retriever = MediaMetadataRetriever() diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/ProjectsRoute.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/ProjectsRoute.kt index 180382c5..f46c282b 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/ProjectsRoute.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/ProjectsRoute.kt @@ -9,6 +9,7 @@ import com.tejpratapsingh.lyricsmaker.presentation.viewmodel.ProjectsViewModel import com.tejpratapsingh.motionstore.tables.MotionProject @Composable +@Suppress("ktlint:standard:function-naming") fun ProjectsRoute( viewModel: ProjectsViewModel, onCreateNew: () -> Unit, diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/ProjectsScreen.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/ProjectsScreen.kt index 8035b181..122779f9 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/ProjectsScreen.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/ProjectsScreen.kt @@ -42,6 +42,7 @@ import com.tejpratapsingh.motionstore.dao.MotionProjectDao import com.tejpratapsingh.motionstore.tables.MotionProject @Composable +@Suppress("ktlint:standard:function-naming") fun ProjectsScreen( projects: List, isRefreshing: Boolean, diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/ThumbnailCache.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/ThumbnailCache.kt index 7f248b7e..6c6f6a02 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/ThumbnailCache.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/projects/ThumbnailCache.kt @@ -2,21 +2,25 @@ package com.tejpratapsingh.lyricsmaker.presentation.compose.projects import android.graphics.Bitmap import android.util.LruCache -import java.util.* object ThumbnailCache { private val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt() private val cacheSize = maxMemory / 8 // Use 1/8th of available memory - private val cache = object : LruCache(cacheSize) { - override fun sizeOf(key: String, value: Bitmap): Int { - return value.byteCount / 1024 + private val cache = + object : LruCache(cacheSize) { + override fun sizeOf( + key: String, + value: Bitmap, + ): Int = value.byteCount / 1024 } - } fun get(key: String): Bitmap? = cache.get(key) - fun put(key: String, bitmap: Bitmap) { + fun put( + key: String, + bitmap: Bitmap, + ) { cache.put(key, bitmap) } diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/search/SearchScreen.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/search/SearchScreen.kt index ac272f51..087787d9 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/search/SearchScreen.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/search/SearchScreen.kt @@ -1,34 +1,24 @@ package com.tejpratapsingh.lyricsmaker.presentation.compose.search -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.History -import androidx.compose.material.icons.rounded.MusicNote -import androidx.compose.material.icons.rounded.Timer +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Search import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon -import androidx.compose.material3.ListItem -import androidx.compose.material3.ListItemDefaults +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Surface @@ -36,53 +26,37 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment -import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import com.tejpratapsingh.lyricsmaker.data.api.lrclib.model.LyricsResponse -import com.tejpratapsingh.lyricsmaker.data.store.RecentSearchHelper import com.tejpratapsingh.lyricsmaker.presentation.viewmodel.LyricsUiState import com.tejpratapsingh.lyricsmaker.presentation.viewmodel.LyricsViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable +@Suppress("FunctionName") fun SearchScreen( - modifier: Modifier = Modifier, viewModel: LyricsViewModel, + modifier: Modifier = Modifier, + onBack: () -> Unit = {}, onLyricsSelected: (LyricsResponse) -> Unit = {}, ) { - val context = LocalContext.current - val query = viewModel.query.collectAsState() - val uiState = viewModel.uiState.collectAsState() - val recentSearches = remember { mutableStateOf(RecentSearchHelper.getSearches(context)) } - - val keyboardController = LocalSoftwareKeyboardController.current + val uiState by viewModel.uiState.collectAsState() + val query by viewModel.query.collectAsState() val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState()) var headerHeightPx by remember { mutableFloatStateOf(0f) } - LaunchedEffect(headerHeightPx) { - scrollBehavior.state.heightOffsetLimit = -headerHeightPx - } - Box( modifier = modifier @@ -99,10 +73,9 @@ fun SearchScreen( x = 0, y = (headerHeightPx + scrollBehavior.state.heightOffset).toInt(), ) - } - .padding(horizontal = 16.dp), + }.padding(horizontal = 16.dp), ) { - when (val state = uiState.value) { + when (val state = uiState) { is LyricsUiState.Success -> { LazyColumn( modifier = Modifier.fillMaxSize(), @@ -113,105 +86,47 @@ fun SearchScreen( Modifier .fillMaxWidth() .padding(vertical = 8.dp), - colors = - CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f), - ), ) { - state.lyrics.forEachIndexed { index, lyrics -> - ListItem( - headlineContent = { - Text( - text = "${lyrics.trackName} - ${lyrics.artistName}", - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold, - ) - }, - supportingContent = { - Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { - Row(verticalAlignment = Alignment.CenterVertically) { - Icon( - imageVector = Icons.Rounded.Timer, - contentDescription = null, - modifier = Modifier.size(16.dp), - tint = MaterialTheme.colorScheme.onSurfaceVariant, - ) - Spacer(Modifier.width(4.dp)) - Text( - text = lyrics.getReadableDuration(), - style = MaterialTheme.typography.bodyMedium, - ) - } - Text( - text = lyrics.getLyrics(), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.bodySmall, - ) - } - }, - leadingContent = { - Icon( - imageVector = Icons.Rounded.MusicNote, - contentDescription = null, - modifier = Modifier.size(24.dp), - ) - }, - colors = ListItemDefaults.colors(containerColor = Color.Transparent), - modifier = Modifier.clickable { onLyricsSelected(lyrics) }, + Column(modifier = Modifier.padding(16.dp)) { + Text( + "Found ${state.lyrics.size} results", + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.primary, ) - if (index < state.lyrics.size - 1) { - HorizontalDivider( - modifier = Modifier.padding(horizontal = 16.dp), - thickness = 0.5.dp, - color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.2f), - ) - } } } } - } - } - else -> { - LazyColumn( - modifier = Modifier.fillMaxSize(), - ) { - item { - Spacer(modifier = Modifier.height(16.dp)) - if (recentSearches.value.isNotEmpty()) { - Text("Recent Searches:", style = MaterialTheme.typography.titleMedium) - Spacer(modifier = Modifier.height(8.dp)) - Card( - modifier = Modifier.fillMaxWidth(), - colors = - CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f), - ), + items(state.lyrics) { lyric -> + Card( + onClick = { + viewModel.selectedLyric.value = lyric + onLyricsSelected(lyric) + }, + modifier = + Modifier + .fillMaxWidth() + .padding(vertical = 4.dp), + ) { + Row( + modifier = Modifier.padding(16.dp), + verticalAlignment = Alignment.CenterVertically, ) { - recentSearches.value.forEachIndexed { index, search -> - ListItem( - headlineContent = { Text(search) }, - leadingContent = { - Icon( - imageVector = Icons.Rounded.History, - contentDescription = null, - modifier = Modifier.size(20.dp), - ) - }, - colors = ListItemDefaults.colors(containerColor = Color.Transparent), - modifier = - Modifier.clickable { - viewModel.query.tryEmit(search) - keyboardController?.hide() - viewModel.searchLyrics(search) - }, + Column(modifier = Modifier.weight(1f)) { + Text( + text = lyric.trackName ?: "", + style = MaterialTheme.typography.titleMedium, ) - if (index < recentSearches.value.size - 1) { - HorizontalDivider( - modifier = Modifier.padding(horizontal = 16.dp), - thickness = 0.5.dp, - color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.2f), + Text( + text = lyric.artistName ?: "", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + if (!lyric.albumName.isNullOrEmpty()) { + Text( + text = lyric.albumName!!, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, ) } } @@ -220,6 +135,43 @@ fun SearchScreen( } } } + + is LyricsUiState.Error -> { + Box(modifier = Modifier.fillMaxSize()) { + Text( + text = state.message, + color = MaterialTheme.colorScheme.error, + modifier = Modifier.align(Alignment.Center), + ) + } + } + + is LyricsUiState.Loading -> { + Box(modifier = Modifier.fillMaxSize()) { + CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) + } + } + + else -> { + Box(modifier = Modifier.fillMaxSize()) { + Column( + modifier = Modifier.align(Alignment.Center), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Icon( + Icons.Default.Search, + contentDescription = null, + modifier = Modifier.size(64.dp), + tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.3f), + ) + Text( + "Enter a song or artist name to search", + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + } } } @@ -230,14 +182,12 @@ fun SearchScreen( .fillMaxWidth() .onGloballyPositioned { headerHeightPx = it.size.height.toFloat() - } - .offset { + }.offset { IntOffset( x = 0, y = scrollBehavior.state.heightOffset.toInt(), ) - } - .statusBarsPadding() + }.statusBarsPadding() .zIndex(1f), color = MaterialTheme.colorScheme.surface, ) { @@ -247,38 +197,27 @@ fun SearchScreen( Text( text = "Search Lyrics", style = MaterialTheme.typography.headlineLarge, - modifier = - Modifier - .align(CenterHorizontally) - .padding(16.dp), + modifier = Modifier.padding(bottom = 16.dp), ) + OutlinedTextField( - value = query.value, - onValueChange = { viewModel.query.tryEmit(it) }, - label = { Text("Search") }, - singleLine = true, + value = query, + onValueChange = { viewModel.query.value = it }, + modifier = Modifier.fillMaxWidth(), + placeholder = { Text("Search songs, artists...") }, + leadingIcon = { + IconButton(onClick = onBack) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") + } + }, trailingIcon = { - if (uiState.value is LyricsUiState.Loading) { - CircularProgressIndicator( - modifier = Modifier.size(20.dp), - strokeWidth = 2.dp, - ) + if (query.isNotEmpty()) { + IconButton(onClick = { viewModel.query.value = "" }) { + Icon(Icons.Default.Search, contentDescription = "Clear") + } } }, - keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Search), - keyboardActions = - KeyboardActions( - onSearch = { - val searchQuery = query.value.trim() - if (searchQuery.isNotBlank()) { - keyboardController?.hide() - RecentSearchHelper.saveSearch(context, searchQuery) - recentSearches.value = RecentSearchHelper.getSearches(context) - viewModel.searchLyrics(query = searchQuery) - } - }, - ), - modifier = Modifier.fillMaxWidth(), + singleLine = true, ) } } diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/templates/LyricsTemplateSelector.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/templates/LyricsTemplateSelector.kt index f1904b36..952c06e2 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/templates/LyricsTemplateSelector.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/templates/LyricsTemplateSelector.kt @@ -2,6 +2,7 @@ package com.tejpratapsingh.lyricsmaker.presentation.compose.templates import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -11,7 +12,6 @@ import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.PagerDefaults import androidx.compose.foundation.pager.rememberPagerState @@ -36,6 +36,7 @@ import com.tejpratapsingh.motionlib.templates.model.MotionTemplate import com.tejpratapsingh.motionstore.tables.MotionProject @Composable +@Suppress("ktlint:standard:function-naming") fun LyricsTemplateSelector( project: MotionProject, onBack: () -> Unit, diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/templates/TemplatePreviewItem.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/templates/TemplatePreviewItem.kt index 8024d1fc..d30dd5bf 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/templates/TemplatePreviewItem.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/templates/TemplatePreviewItem.kt @@ -14,8 +14,8 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp @@ -28,6 +28,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @Composable +@Suppress("ktlint:standard:function-naming") internal fun TemplatePreviewItem( project: MotionProject, template: MotionTemplate, diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/templates/ZoomLyricsTemplate.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/templates/ZoomLyricsTemplate.kt index e7e38326..8f0251d0 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/templates/ZoomLyricsTemplate.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/templates/ZoomLyricsTemplate.kt @@ -3,7 +3,6 @@ package com.tejpratapsingh.lyricsmaker.presentation.templates import android.view.Gravity import androidx.appcompat.widget.AppCompatTextView import androidx.core.net.toUri -import androidx.core.view.isVisible import com.tejpratapsingh.lyricsmaker.data.lrc.SyncedLyricFrame import com.tejpratapsingh.motionlib.core.MotionTextVariant import com.tejpratapsingh.motionlib.core.motion.transitions.CrossFadeTransition diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/ui/theme/Theme.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/ui/theme/Theme.kt index 6aa53c61..7fbba2fd 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/ui/theme/Theme.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/ui/theme/Theme.kt @@ -34,6 +34,7 @@ private val LightColorScheme = ) @Composable +@Suppress("ktlint:standard:function-naming") fun AnimatorTheme( darkTheme: Boolean = isSystemInDarkTheme(), // Dynamic color is available on Android 12+ diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/viewmodel/LyricsViewModel.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/viewmodel/LyricsViewModel.kt index 0ef06168..bcc9b05a 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/viewmodel/LyricsViewModel.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/viewmodel/LyricsViewModel.kt @@ -104,11 +104,12 @@ class LyricsViewModel : ViewModel() { var selectedLyrics: List = emptyList() set(value) { field = value - selectedStartTimeInSeconds = if (value.isNotEmpty()) { - value.first().frame.toFloat() / provideCurrentConfig().fps - } else { - 0f - } + selectedStartTimeInSeconds = + if (value.isNotEmpty()) { + value.first().frame.toFloat() / provideCurrentConfig().fps + } else { + 0f + } } get() { if (field.isEmpty()) return emptyList() diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/worker/LyricsMotionWorkerCancelReceiver.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/worker/LyricsMotionWorkerCancelReceiver.kt index eff6eea4..694e3069 100644 --- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/worker/LyricsMotionWorkerCancelReceiver.kt +++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/worker/LyricsMotionWorkerCancelReceiver.kt @@ -12,7 +12,10 @@ class LyricsMotionWorkerCancelReceiver : BroadcastReceiver() { const val EXTRA_WORK_ID = "extra_work_id" } - override fun onReceive(context: Context, intent: Intent) { + override fun onReceive( + context: Context, + intent: Intent, + ) { if (intent.action == ACTION_CANCEL) { val workIdString = intent.getStringExtra(EXTRA_WORK_ID) if (workIdString != null) { diff --git a/modules/lyrics-maker/src/test/java/com/tejpratapsingh/lyricsmaker/presentation/compose/ScreenTest.kt b/modules/lyrics-maker/src/test/java/com/tejpratapsingh/lyricsmaker/presentation/compose/ScreenTest.kt index dccb3535..12077039 100644 --- a/modules/lyrics-maker/src/test/java/com/tejpratapsingh/lyricsmaker/presentation/compose/ScreenTest.kt +++ b/modules/lyrics-maker/src/test/java/com/tejpratapsingh/lyricsmaker/presentation/compose/ScreenTest.kt @@ -11,7 +11,6 @@ import org.junit.Test * helper introduced in this PR. */ class ScreenTest { - // ------------------------------------------------------------------ // Screen.ProjectDetails.route template // ------------------------------------------------------------------ @@ -99,4 +98,4 @@ class ScreenTest { fun `Lyrics screen has route 'lyrics'`() { assertEquals("lyrics", Screen.Lyrics.route) } -} \ No newline at end of file +} diff --git a/modules/lyrics-maker/src/test/java/com/tejpratapsingh/lyricsmaker/presentation/viewmodel/LyricsViewModelTest.kt b/modules/lyrics-maker/src/test/java/com/tejpratapsingh/lyricsmaker/presentation/viewmodel/LyricsViewModelTest.kt index 9a740416..1c366bcb 100644 --- a/modules/lyrics-maker/src/test/java/com/tejpratapsingh/lyricsmaker/presentation/viewmodel/LyricsViewModelTest.kt +++ b/modules/lyrics-maker/src/test/java/com/tejpratapsingh/lyricsmaker/presentation/viewmodel/LyricsViewModelTest.kt @@ -13,7 +13,6 @@ import org.junit.Test * and the new selectedStartTimeInSeconds field introduced in this PR. */ class LyricsViewModelTest { - private lateinit var viewModel: LyricsViewModel @Before @@ -40,10 +39,11 @@ class LyricsViewModelTest { @Test fun `selectedLyrics getter normalises frames by subtracting the first frame`() { - viewModel.selectedLyrics = listOf( - SyncedLyricFrame(frame = 48, text = "Hello"), - SyncedLyricFrame(frame = 96, text = "World"), - ) + viewModel.selectedLyrics = + listOf( + SyncedLyricFrame(frame = 48, text = "Hello"), + SyncedLyricFrame(frame = 96, text = "World"), + ) val result = viewModel.selectedLyrics @@ -56,10 +56,11 @@ class LyricsViewModelTest { @Test fun `selectedLyrics getter preserves texts after frame normalisation`() { - viewModel.selectedLyrics = listOf( - SyncedLyricFrame(frame = 10, text = "Line A"), - SyncedLyricFrame(frame = 20, text = "Line B"), - ) + viewModel.selectedLyrics = + listOf( + SyncedLyricFrame(frame = 10, text = "Line A"), + SyncedLyricFrame(frame = 20, text = "Line B"), + ) val result = viewModel.selectedLyrics @@ -70,10 +71,11 @@ class LyricsViewModelTest { @Test fun `selectedLyrics getter sorts frames in ascending order`() { // Provide frames out of order to verify sorting. - viewModel.selectedLyrics = listOf( - SyncedLyricFrame(frame = 100, text = "Second"), - SyncedLyricFrame(frame = 50, text = "First"), - ) + viewModel.selectedLyrics = + listOf( + SyncedLyricFrame(frame = 100, text = "Second"), + SyncedLyricFrame(frame = 50, text = "First"), + ) val result = viewModel.selectedLyrics @@ -87,9 +89,10 @@ class LyricsViewModelTest { @Test fun `selectedLyrics getter returns single item with frame normalised to zero`() { - viewModel.selectedLyrics = listOf( - SyncedLyricFrame(frame = 72, text = "Only line"), - ) + viewModel.selectedLyrics = + listOf( + SyncedLyricFrame(frame = 72, text = "Only line"), + ) val result = viewModel.selectedLyrics @@ -112,10 +115,11 @@ class LyricsViewModelTest { @Test fun `selectedStartTimeInSeconds is computed from first frame divided by fps`() { // fps = 24 (set in setUp), first frame = 48 → expected = 48 / 24 = 2.0 seconds - viewModel.selectedLyrics = listOf( - SyncedLyricFrame(frame = 48, text = "Start"), - SyncedLyricFrame(frame = 72, text = "End"), - ) + viewModel.selectedLyrics = + listOf( + SyncedLyricFrame(frame = 48, text = "Start"), + SyncedLyricFrame(frame = 72, text = "End"), + ) assertEquals(2.0f, viewModel.selectedStartTimeInSeconds, 0.001f) } @@ -146,12 +150,13 @@ class LyricsViewModelTest { // the raw first element — NOT the minimum frame. Verify this contract. val fps = 24 val firstFrame = 72 // This will be value.first() - viewModel.selectedLyrics = listOf( - SyncedLyricFrame(frame = firstFrame, text = "C"), - SyncedLyricFrame(frame = 24, text = "A"), - ) + viewModel.selectedLyrics = + listOf( + SyncedLyricFrame(frame = firstFrame, text = "C"), + SyncedLyricFrame(frame = 24, text = "A"), + ) val expected = firstFrame.toFloat() / fps assertEquals(expected, viewModel.selectedStartTimeInSeconds, 0.001f) } -} \ No newline at end of file +} diff --git a/modules/media3-motion-ext/src/main/AndroidManifest.xml b/modules/media3-motion-ext/src/main/AndroidManifest.xml index 94cbbcfc..cc947c56 100644 --- a/modules/media3-motion-ext/src/main/AndroidManifest.xml +++ b/modules/media3-motion-ext/src/main/AndroidManifest.xml @@ -1 +1 @@ - + diff --git a/modules/media3-motion-ext/src/main/java/com/tejpratapsingh/motionlib/media3/FrameProcessor.kt b/modules/media3-motion-ext/src/main/java/com/tejpratapsingh/motionlib/media3/FrameProcessor.kt index 7eb7960e..2282f1fb 100644 --- a/modules/media3-motion-ext/src/main/java/com/tejpratapsingh/motionlib/media3/FrameProcessor.kt +++ b/modules/media3-motion-ext/src/main/java/com/tejpratapsingh/motionlib/media3/FrameProcessor.kt @@ -14,7 +14,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import timber.log.Timber -import java.io.File import java.util.Locale import java.util.concurrent.atomic.AtomicInteger @@ -22,7 +21,7 @@ import java.util.concurrent.atomic.AtomicInteger * Handles frame generation and caching for video production. */ class FrameProcessor( - private val subDirName: String + private val subDirName: String, ) { /** * Renders frames from [motionComposerView] and saves them to the cache directory. diff --git a/modules/media3-motion-ext/src/main/java/com/tejpratapsingh/motionlib/media3/Media3CompositionBuilder.kt b/modules/media3-motion-ext/src/main/java/com/tejpratapsingh/motionlib/media3/Media3CompositionBuilder.kt index 024ae9f6..e7ed7512 100644 --- a/modules/media3-motion-ext/src/main/java/com/tejpratapsingh/motionlib/media3/Media3CompositionBuilder.kt +++ b/modules/media3-motion-ext/src/main/java/com/tejpratapsingh/motionlib/media3/Media3CompositionBuilder.kt @@ -17,7 +17,6 @@ import kotlin.math.roundToLong */ @UnstableApi class Media3CompositionBuilder { - /** * Builds a [Composition] from the frames in [frameDirectory] and the provided [motionAudio]. */ @@ -95,9 +94,13 @@ class Media3CompositionBuilder { } } - private fun frameToMs(frame: Int, fps: Int): Long = - ((frame.toDouble() / fps.toDouble()) * 1_000).roundToLong() + private fun frameToMs( + frame: Int, + fps: Int, + ): Long = ((frame.toDouble() / fps.toDouble()) * 1_000).roundToLong() - private fun frameToUs(frame: Int, fps: Int): Long = - ((frame.toDouble() / fps.toDouble()) * 1_000_000).roundToLong() + private fun frameToUs( + frame: Int, + fps: Int, + ): Long = ((frame.toDouble() / fps.toDouble()) * 1_000_000).roundToLong() } diff --git a/modules/media3-motion-ext/src/main/java/com/tejpratapsingh/motionlib/media3/Media3TransformerRunner.kt b/modules/media3-motion-ext/src/main/java/com/tejpratapsingh/motionlib/media3/Media3TransformerRunner.kt index 8fc9fb70..6c3ed9a0 100644 --- a/modules/media3-motion-ext/src/main/java/com/tejpratapsingh/motionlib/media3/Media3TransformerRunner.kt +++ b/modules/media3-motion-ext/src/main/java/com/tejpratapsingh/motionlib/media3/Media3TransformerRunner.kt @@ -22,7 +22,6 @@ import kotlin.coroutines.resumeWithException */ @UnstableApi class Media3TransformerRunner { - /** * Exports the [composition] to [outputFile] using Media3 [Transformer]. */ diff --git a/modules/media3-motion-ext/src/main/java/com/tejpratapsingh/motionlib/media3/Media3VideoProducerAdapter.kt b/modules/media3-motion-ext/src/main/java/com/tejpratapsingh/motionlib/media3/Media3VideoProducerAdapter.kt index 847ded8e..7353c4ea 100644 --- a/modules/media3-motion-ext/src/main/java/com/tejpratapsingh/motionlib/media3/Media3VideoProducerAdapter.kt +++ b/modules/media3-motion-ext/src/main/java/com/tejpratapsingh/motionlib/media3/Media3VideoProducerAdapter.kt @@ -21,7 +21,6 @@ class Media3VideoProducerAdapter( private val compositionBuilder: Media3CompositionBuilder = Media3CompositionBuilder(), private val transformerRunner: Media3TransformerRunner = Media3TransformerRunner(), ) : VideoProducerAdapter { - private val subDirName by lazy { UUID.randomUUID().toString() } override suspend fun produceVideo( @@ -56,17 +55,18 @@ class Media3VideoProducerAdapter( ) // S: Composition building responsibility delegated to Media3CompositionBuilder - val composition = compositionBuilder.build( - frameDirectory = subDir, - motionAudio = motionAudio, - motionConfig = motionConfig - ) + val composition = + compositionBuilder.build( + frameDirectory = subDir, + motionAudio = motionAudio, + motionConfig = motionConfig, + ) // S: Transformer execution responsibility delegated to Media3TransformerRunner transformerRunner.export( context = context, composition = composition, - outputFile = outputFile + outputFile = outputFile, ) } finally { if (subDir.exists()) { diff --git a/modules/ml-kit-ext/src/main/java/com/tejpratapsingh/motionlib/mlkit/plugins/SubjectSegmentationPlugin.kt b/modules/ml-kit-ext/src/main/java/com/tejpratapsingh/motionlib/mlkit/plugins/SubjectSegmentationPlugin.kt index d58fd280..01da765e 100644 --- a/modules/ml-kit-ext/src/main/java/com/tejpratapsingh/motionlib/mlkit/plugins/SubjectSegmentationPlugin.kt +++ b/modules/ml-kit-ext/src/main/java/com/tejpratapsingh/motionlib/mlkit/plugins/SubjectSegmentationPlugin.kt @@ -14,9 +14,9 @@ import java.nio.FloatBuffer * This processes the bitmap directly and returns a new bitmap with the background removed. */ class SubjectSegmentationPlugin : MotionPlugin { - private val options = - SubjectSegmenterOptions.Builder() + SubjectSegmenterOptions + .Builder() .enableForegroundConfidenceMask() .build() @@ -40,27 +40,28 @@ class SubjectSegmentationPlugin : MotionPlugin { private fun applyMaskToBitmap( source: Bitmap, - mask: FloatBuffer + mask: FloatBuffer, ): Bitmap { val width = source.width val height = source.height val result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) - + mask.rewind() val pixels = IntArray(width * height) source.getPixels(pixels, 0, width, 0, 0, width, height) - + for (i in 0 until width * height) { val confidence = if (mask.hasRemaining()) mask.get() else 0.0f val alpha = (Color.alpha(pixels[i]) * confidence).toInt() - pixels[i] = Color.argb( - alpha, - Color.red(pixels[i]), - Color.green(pixels[i]), - Color.blue(pixels[i]) - ) + pixels[i] = + Color.argb( + alpha, + Color.red(pixels[i]), + Color.green(pixels[i]), + Color.blue(pixels[i]), + ) } - + result.setPixels(pixels, 0, width, 0, 0, width, height) return result } diff --git a/modules/motion-store/src/main/java/com/tejpratapsingh/motionstore/dao/MotionProjectDao.kt b/modules/motion-store/src/main/java/com/tejpratapsingh/motionstore/dao/MotionProjectDao.kt index a5e75af8..fd28f6bf 100644 --- a/modules/motion-store/src/main/java/com/tejpratapsingh/motionstore/dao/MotionProjectDao.kt +++ b/modules/motion-store/src/main/java/com/tejpratapsingh/motionstore/dao/MotionProjectDao.kt @@ -87,11 +87,10 @@ class MotionProjectDao( ), ) - private fun String.toJsonObject(): JsonObject { - return try { + private fun String.toJsonObject(): JsonObject = + try { JsonParser.parseString(this).asJsonObject } catch (e: Exception) { JsonObject() } - } } diff --git a/modules/motion-store/src/main/java/com/tejpratapsingh/motionstore/infra/PreferenceManager.kt b/modules/motion-store/src/main/java/com/tejpratapsingh/motionstore/infra/PreferenceManager.kt index 2e1e3585..db0f0f4b 100644 --- a/modules/motion-store/src/main/java/com/tejpratapsingh/motionstore/infra/PreferenceManager.kt +++ b/modules/motion-store/src/main/java/com/tejpratapsingh/motionstore/infra/PreferenceManager.kt @@ -4,7 +4,9 @@ import android.content.Context import android.content.SharedPreferences import com.tejpratapsingh.motionstore.dao.MotionProjectDao -class PreferenceManager(context: Context) { +class PreferenceManager( + context: Context, +) { private val prefs: SharedPreferences = context.getSharedPreferences("lyrics_maker_prefs", Context.MODE_PRIVATE) diff --git a/modules/motion-video-editor/src/main/java/com/tejpratapsingh/motioneditor/TimelineData.kt b/modules/motion-video-editor/src/main/java/com/tejpratapsingh/motioneditor/TimelineData.kt index 0ee93357..26b6b781 100644 --- a/modules/motion-video-editor/src/main/java/com/tejpratapsingh/motioneditor/TimelineData.kt +++ b/modules/motion-video-editor/src/main/java/com/tejpratapsingh/motioneditor/TimelineData.kt @@ -2,7 +2,7 @@ package com.tejpratapsingh.motioneditor data class TimelineTrack( val id: String, - val items: List + val items: List, ) data class TimelineItem( @@ -10,5 +10,5 @@ data class TimelineItem( val type: String, val startFrame: Int, val endFrame: Int, - val label: String + val label: String, ) diff --git a/modules/motion-video-editor/src/main/java/com/tejpratapsingh/motioneditor/ui/MotionEditorScreen.kt b/modules/motion-video-editor/src/main/java/com/tejpratapsingh/motioneditor/ui/MotionEditorScreen.kt index a9ec2199..205d2b72 100644 --- a/modules/motion-video-editor/src/main/java/com/tejpratapsingh/motioneditor/ui/MotionEditorScreen.kt +++ b/modules/motion-video-editor/src/main/java/com/tejpratapsingh/motioneditor/ui/MotionEditorScreen.kt @@ -1,7 +1,9 @@ package com.tejpratapsingh.motioneditor.ui import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -9,6 +11,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack @@ -20,14 +23,11 @@ import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.foundation.gestures.detectDragGestures -import androidx.compose.foundation.layout.BoxWithConstraints -import androidx.compose.foundation.layout.width -import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity @@ -42,6 +42,7 @@ import com.tejpratapsingh.motionstore.tables.MotionProject import com.tejpratapsingh.motionstore.tables.SyncTracker @Composable +@Suppress("ktlint:standard:function-naming") fun MotionEditorScreen( project: MotionProject, onBackClick: () -> Unit, @@ -180,6 +181,7 @@ fun MotionEditorScreen( @Preview(showBackground = true) @Composable +@Suppress("ktlint:standard:function-naming") fun PreviewMotionEditorScreen() { val sampleProject = MotionProject( diff --git a/modules/motion-video-editor/src/main/java/com/tejpratapsingh/motioneditor/ui/MotionTimeline.kt b/modules/motion-video-editor/src/main/java/com/tejpratapsingh/motioneditor/ui/MotionTimeline.kt index a01ed90d..f3d445ac 100644 --- a/modules/motion-video-editor/src/main/java/com/tejpratapsingh/motioneditor/ui/MotionTimeline.kt +++ b/modules/motion-video-editor/src/main/java/com/tejpratapsingh/motioneditor/ui/MotionTimeline.kt @@ -15,10 +15,13 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -32,23 +35,21 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.hapticfeedback.HapticFeedbackType +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.Surface -import androidx.compose.ui.text.style.TextOverflow import com.tejpratapsingh.motioneditor.TimelineItem import com.tejpratapsingh.motioneditor.TimelineTrack -import androidx.compose.ui.input.pointer.pointerInput import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map @Composable +@Suppress("ktlint:standard:function-naming") fun MotionTimeline( tracks: List, currentFrame: Int, @@ -116,8 +117,7 @@ fun MotionTimeline( change.consume() onResize(dragAmount) } - } - .horizontalScroll(horizontalScrollState), + }.horizontalScroll(horizontalScrollState), ) { Row { Spacer(modifier = Modifier.width(halfWidth)) @@ -168,6 +168,7 @@ fun MotionTimeline( } @Composable +@Suppress("ktlint:standard:function-naming") fun TimeScaleView( totalFrames: Int, fps: Int, @@ -229,6 +230,7 @@ private fun formatFrameToTime( } @Composable +@Suppress("ktlint:standard:function-naming") fun TimelineTrackView( track: TimelineTrack, pixelsPerFrame: Float, @@ -249,6 +251,7 @@ fun TimelineTrackView( } @Composable +@Suppress("ktlint:standard:function-naming") fun TimelineItemView( item: TimelineItem, pixelsPerFrame: Float, @@ -276,7 +279,11 @@ fun TimelineItemView( ) { Box(contentAlignment = Alignment.Center) { Text( - text = item.label.firstOrNull()?.toString()?.uppercase() ?: "?", + text = + item.label + .firstOrNull() + ?.toString() + ?.uppercase() ?: "?", color = MaterialTheme.colorScheme.onPrimary, style = MaterialTheme.typography.labelLarge, ) @@ -306,6 +313,7 @@ fun TimelineItemView( @Preview(showBackground = true) @Composable +@Suppress("ktlint:standard:function-naming") fun PreviewMotionTimeline() { val sampleTracks = listOf( diff --git a/modules/motion-video-editor/src/main/java/com/tejpratapsingh/motioneditor/utils/TimelineUtils.kt b/modules/motion-video-editor/src/main/java/com/tejpratapsingh/motioneditor/utils/TimelineUtils.kt index 06b86cff..26e66396 100644 --- a/modules/motion-video-editor/src/main/java/com/tejpratapsingh/motioneditor/utils/TimelineUtils.kt +++ b/modules/motion-video-editor/src/main/java/com/tejpratapsingh/motioneditor/utils/TimelineUtils.kt @@ -8,7 +8,10 @@ import com.tejpratapsingh.motioneditor.TimelineTrack import com.tejpratapsingh.motionlib.core.MotionView object TimelineUtils { - fun fromSdui(context: Context, sduiJson: JsonObject): List { + fun fromSdui( + context: Context, + sduiJson: JsonObject, + ): List { val views = sduiJson.getMotionViews(context) return fromMotionViews(views) } @@ -19,15 +22,16 @@ object TimelineUtils { return views.mapIndexed { index, view -> TimelineTrack( id = "track_$index", - items = listOf( - TimelineItem( - id = view.hashCode().toString(), - type = view.javaClass.simpleName, - startFrame = view.startFrame, - endFrame = view.endFrame, - label = view.javaClass.simpleName - ) - ) + items = + listOf( + TimelineItem( + id = view.hashCode().toString(), + type = view.javaClass.simpleName, + startFrame = view.startFrame, + endFrame = view.endFrame, + label = view.javaClass.simpleName, + ), + ), ) } } diff --git a/modules/motion-video-player/src/main/java/com/tejpratapsingh/motionlib/ui/custom/video/MotionVideoPlayerCompose.kt b/modules/motion-video-player/src/main/java/com/tejpratapsingh/motionlib/ui/custom/video/MotionVideoPlayerCompose.kt index 285f9c2e..7b91ca5f 100644 --- a/modules/motion-video-player/src/main/java/com/tejpratapsingh/motionlib/ui/custom/video/MotionVideoPlayerCompose.kt +++ b/modules/motion-video-player/src/main/java/com/tejpratapsingh/motionlib/ui/custom/video/MotionVideoPlayerCompose.kt @@ -47,6 +47,7 @@ import kotlinx.coroutines.delay import java.util.Locale @Composable +@Suppress("FunctionName") fun MotionVideoPlayerCompose( motionVideoProducer: MotionVideoProducer, modifier: Modifier = Modifier, diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/animation/Spring.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/animation/Spring.kt index 7719d621..227a9d1f 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/animation/Spring.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/animation/Spring.kt @@ -99,9 +99,9 @@ object Spring { // numerically degenerate; fallback 1.0 - exp(-omega0 * t) } else { - val A = (v0 - r2 * (-1.0)) / (r1 - r2) // solving linear system - val B = -1.0 - A - 1.0 + A * exp(r1 * t) + B * exp(r2 * t) + val a = (v0 - r2 * (-1.0)) / (r1 - r2) // solving linear system + val b = -1.0 - a + 1.0 + a * exp(r1 * t) + b * exp(r2 * t) } } } diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/MotionVideoProducer.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/MotionVideoProducer.kt index 3b398a38..9af4be07 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/MotionVideoProducer.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/core/motion/MotionVideoProducer.kt @@ -51,7 +51,7 @@ open class MotionVideoProducer private constructor( override fun addTransition( transition: MotionTransition, - duration: Int + duration: Int, ): MotionVideoProducer { pendingTransition = Pair(transition, duration) return this @@ -66,31 +66,33 @@ open class MotionVideoProducer private constructor( transition.apply(currentLastView, motionView, duration) pendingTransition = null } - + lastMotionView = motionView totalFrames = maxOf(totalFrames, motionView.endFrame) motionComposerView.apply { val layoutInfo = motionView.layoutInfo motionView.layoutBy( - x = if (layoutInfo.gravity and android.view.Gravity.CENTER_HORIZONTAL == android.view.Gravity.CENTER_HORIZONTAL) { - centerHorizontallyTo { parent.centerX() } - } else if (layoutInfo.gravity and android.view.Gravity.LEFT == android.view.Gravity.LEFT) { - leftTo { parent.left() + layoutInfo.margin.left.toXInt() } - } else if (layoutInfo.gravity and android.view.Gravity.RIGHT == android.view.Gravity.RIGHT) { - rightTo { parent.right() - layoutInfo.margin.right.toXInt() } - } else { - centerHorizontallyTo { parent.centerX() } - }, - y = if (layoutInfo.gravity and android.view.Gravity.CENTER_VERTICAL == android.view.Gravity.CENTER_VERTICAL) { - centerVerticallyTo { parent.centerY() } - } else if (layoutInfo.gravity and android.view.Gravity.TOP == android.view.Gravity.TOP) { - topTo { parent.top() + layoutInfo.margin.top.toYInt() } - } else if (layoutInfo.gravity and android.view.Gravity.BOTTOM == android.view.Gravity.BOTTOM) { - bottomTo { parent.bottom() - layoutInfo.margin.bottom.toYInt() } - } else { - centerVerticallyTo { parent.centerY() } - }, + x = + if (layoutInfo.gravity and android.view.Gravity.CENTER_HORIZONTAL == android.view.Gravity.CENTER_HORIZONTAL) { + centerHorizontallyTo { parent.centerX() } + } else if (layoutInfo.gravity and android.view.Gravity.LEFT == android.view.Gravity.LEFT) { + leftTo { parent.left() + layoutInfo.margin.left.toXInt() } + } else if (layoutInfo.gravity and android.view.Gravity.RIGHT == android.view.Gravity.RIGHT) { + rightTo { parent.right() - layoutInfo.margin.right.toXInt() } + } else { + centerHorizontallyTo { parent.centerX() } + }, + y = + if (layoutInfo.gravity and android.view.Gravity.CENTER_VERTICAL == android.view.Gravity.CENTER_VERTICAL) { + centerVerticallyTo { parent.centerY() } + } else if (layoutInfo.gravity and android.view.Gravity.TOP == android.view.Gravity.TOP) { + topTo { parent.top() + layoutInfo.margin.top.toYInt() } + } else if (layoutInfo.gravity and android.view.Gravity.BOTTOM == android.view.Gravity.BOTTOM) { + bottomTo { parent.bottom() - layoutInfo.margin.bottom.toYInt() } + } else { + centerVerticallyTo { parent.centerY() } + }, addToViewGroup = true, ) } diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/text/abstract/AbstractMotionTextView.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/text/abstract/AbstractMotionTextView.kt index 9a158ffb..27ec8265 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/text/abstract/AbstractMotionTextView.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/custom/text/abstract/AbstractMotionTextView.kt @@ -42,7 +42,6 @@ abstract class AbstractMotionTextView( val highlightColor: String? = null, effects: List = emptyList(), ) : BaseContourMotionView(context, startFrame, endFrame, effects = effects) { - /** * Calculates the adjusted end frame based on the writing speed. * If [writingSpeed] is 1.0, it matches [endFrame]. If higher, the duration is shortened. diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/BlurEffect.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/BlurEffect.kt index 3b6670a3..02fc2206 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/BlurEffect.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/BlurEffect.kt @@ -20,9 +20,9 @@ class BlurEffect( override fun forFrame(frame: Int): MotionView { if (motionView !is View) return motionView - + val view = motionView as View - + if (frame !in startFrame..endFrame) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { view.setRenderEffect(null) @@ -43,8 +43,8 @@ class BlurEffect( RenderEffect.createBlurEffect( blurRadius, blurRadius, - Shader.TileMode.CLAMP - ) + Shader.TileMode.CLAMP, + ), ) } diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/BrightnessContrastEffect.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/BrightnessContrastEffect.kt index 60491a67..912dbdde 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/BrightnessContrastEffect.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/BrightnessContrastEffect.kt @@ -38,31 +38,50 @@ class BrightnessContrastEffect( return motionView } - val brightness = MotionInterpolator.interpolateForRange( - interpolator = Interpolators(Easings.LINEAR), - currentFrame = frame, - frameRange = Pair(startFrame, endFrame), - valueRange = Pair(fromBrightness, toBrightness), - ) - - val contrast = MotionInterpolator.interpolateForRange( - interpolator = Interpolators(Easings.LINEAR), - currentFrame = frame, - frameRange = Pair(startFrame, endFrame), - valueRange = Pair(fromContrast, toContrast), - ) + val brightness = + MotionInterpolator.interpolateForRange( + interpolator = Interpolators(Easings.LINEAR), + currentFrame = frame, + frameRange = Pair(startFrame, endFrame), + valueRange = Pair(fromBrightness, toBrightness), + ) + + val contrast = + MotionInterpolator.interpolateForRange( + interpolator = Interpolators(Easings.LINEAR), + currentFrame = frame, + frameRange = Pair(startFrame, endFrame), + valueRange = Pair(fromContrast, toContrast), + ) // Matrix for contrast and brightness // contrast * (channel - 0.5) + 0.5 + brightness // = contrast * channel - 0.5 * contrast + 0.5 + brightness val t = (1.0f - contrast) / 2.0f * 255.0f + brightness * 255.0f - - val matrix = floatArrayOf( - contrast, 0f, 0f, 0f, t, - 0f, contrast, 0f, 0f, t, - 0f, 0f, contrast, 0f, t, - 0f, 0f, 0f, 1f, 0f - ) + + val matrix = + floatArrayOf( + contrast, + 0f, + 0f, + 0f, + t, + 0f, + contrast, + 0f, + 0f, + t, + 0f, + 0f, + contrast, + 0f, + t, + 0f, + 0f, + 0f, + 1f, + 0f, + ) val colorFilter = ColorMatrixColorFilter(matrix) view.setRenderEffect(RenderEffect.createColorFilterEffect(colorFilter)) diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/ChainEffect.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/ChainEffect.kt index dde977a6..88761ef1 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/ChainEffect.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/ChainEffect.kt @@ -40,13 +40,13 @@ class ChainEffect( } // Chaining is tricky with the current side-effect based architecture. - // For now, we just call the inner and outer effects, which will + // For now, we just call the inner and outer effects, which will // each try to set the RenderEffect on the view, with the last one winning. - // To properly support chaining, we would need to refactor MotionEffect + // To properly support chaining, we would need to refactor MotionEffect // to return a RenderEffect instead of applying it. innerEffect.forFrame(frame) outerEffect.forFrame(frame) - + return motionView } } diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/FadeInEffect.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/FadeInEffect.kt index 27f1fd69..8f10ac0e 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/FadeInEffect.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/FadeInEffect.kt @@ -15,9 +15,9 @@ class FadeInEffect( override fun forFrame(frame: Int): MotionView { if (motionView !is View) return motionView - + val view = motionView as View - + if (frame !in startFrame..endFrame) { // If we are past the effect, ensure alpha is 1 if (frame > endFrame) { diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/FadeOutEffect.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/FadeOutEffect.kt index 1d153fe1..942e4f25 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/FadeOutEffect.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/FadeOutEffect.kt @@ -15,9 +15,9 @@ class FadeOutEffect( override fun forFrame(frame: Int): MotionView { if (motionView !is View) return motionView - + val view = motionView as View - + if (frame !in startFrame..endFrame) { // If we are past the effect, ensure alpha is 0 if (frame > endFrame) { diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/GrayscaleEffect.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/GrayscaleEffect.kt index e796d305..255440e3 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/GrayscaleEffect.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/GrayscaleEffect.kt @@ -32,7 +32,7 @@ class GrayscaleEffect( if (frame !in startFrame..endFrame) { if (frame > endFrame) { - // Keep the final state if needed, or clear it. + // Keep the final state if needed, or clear it. // Typically transitions might want to stay at final state if it's the end of visibility. // But for generic effects, we might want to clear them when out of range. // BlurEffect clears it, so we follow that pattern. @@ -41,17 +41,19 @@ class GrayscaleEffect( return motionView } - val saturation = MotionInterpolator.interpolateForRange( - interpolator = Interpolators(Easings.LINEAR), - currentFrame = frame, - frameRange = Pair(startFrame, endFrame), - valueRange = Pair(fromSaturation, toSaturation), - ) + val saturation = + MotionInterpolator.interpolateForRange( + interpolator = Interpolators(Easings.LINEAR), + currentFrame = frame, + frameRange = Pair(startFrame, endFrame), + valueRange = Pair(fromSaturation, toSaturation), + ) + + val matrix = + ColorMatrix().apply { + setSaturation(saturation) + } - val matrix = ColorMatrix().apply { - setSaturation(saturation) - } - val colorFilter = ColorMatrixColorFilter(matrix) view.setRenderEffect(RenderEffect.createColorFilterEffect(colorFilter)) diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/InvertEffect.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/InvertEffect.kt index c8c21157..763c73a8 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/InvertEffect.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/InvertEffect.kt @@ -35,12 +35,13 @@ class InvertEffect( return motionView } - val intensity = MotionInterpolator.interpolateForRange( - interpolator = Interpolators(Easings.LINEAR), - currentFrame = frame, - frameRange = Pair(startFrame, endFrame), - valueRange = Pair(fromIntensity, toIntensity), - ) + val intensity = + MotionInterpolator.interpolateForRange( + interpolator = Interpolators(Easings.LINEAR), + currentFrame = frame, + frameRange = Pair(startFrame, endFrame), + valueRange = Pair(fromIntensity, toIntensity), + ) // Invert matrix: // R' = -1*R + 255 @@ -48,21 +49,55 @@ class InvertEffect( // B' = -1*B + 255 // Scaled to 0-1 for ColorMatrix: // R' = -1*R + 1 - - val invertMatrix = floatArrayOf( - -1f, 0f, 0f, 0f, 255f, - 0f, -1f, 0f, 0f, 255f, - 0f, 0f, -1f, 0f, 255f, - 0f, 0f, 0f, 1f, 0f - ) - - val identityMatrix = floatArrayOf( - 1f, 0f, 0f, 0f, 0f, - 0f, 1f, 0f, 0f, 0f, - 0f, 0f, 1f, 0f, 0f, - 0f, 0f, 0f, 1f, 0f - ) - + + val invertMatrix = + floatArrayOf( + -1f, + 0f, + 0f, + 0f, + 255f, + 0f, + -1f, + 0f, + 0f, + 255f, + 0f, + 0f, + -1f, + 0f, + 255f, + 0f, + 0f, + 0f, + 1f, + 0f, + ) + + val identityMatrix = + floatArrayOf( + 1f, + 0f, + 0f, + 0f, + 0f, + 0f, + 1f, + 0f, + 0f, + 0f, + 0f, + 0f, + 1f, + 0f, + 0f, + 0f, + 0f, + 0f, + 1f, + 0f, + ) + val resultMatrix = FloatArray(20) for (i in 0 until 20) { resultMatrix[i] = identityMatrix[i] + (invertMatrix[i] - identityMatrix[i]) * intensity diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/OffsetEffect.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/OffsetEffect.kt index e8906213..05e719a1 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/OffsetEffect.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/OffsetEffect.kt @@ -36,19 +36,21 @@ class OffsetEffect( return motionView } - val offsetX = MotionInterpolator.interpolateForRange( - interpolator = Interpolators(Easings.LINEAR), - currentFrame = frame, - frameRange = Pair(startFrame, endFrame), - valueRange = Pair(fromOffsetX, toOffsetX), - ) - - val offsetY = MotionInterpolator.interpolateForRange( - interpolator = Interpolators(Easings.LINEAR), - currentFrame = frame, - frameRange = Pair(startFrame, endFrame), - valueRange = Pair(fromOffsetY, toOffsetY), - ) + val offsetX = + MotionInterpolator.interpolateForRange( + interpolator = Interpolators(Easings.LINEAR), + currentFrame = frame, + frameRange = Pair(startFrame, endFrame), + valueRange = Pair(fromOffsetX, toOffsetX), + ) + + val offsetY = + MotionInterpolator.interpolateForRange( + interpolator = Interpolators(Easings.LINEAR), + currentFrame = frame, + frameRange = Pair(startFrame, endFrame), + valueRange = Pair(fromOffsetY, toOffsetY), + ) view.setRenderEffect(RenderEffect.createOffsetEffect(offsetX, offsetY)) diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/PixelateEffect.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/PixelateEffect.kt index da4c3914..11ccbf56 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/PixelateEffect.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/PixelateEffect.kt @@ -22,7 +22,9 @@ class PixelateEffect( ) : MotionEffect { override lateinit var motionView: MotionView - private val PIXELATE_SHADER = """ + companion object { + private const val PIXELATE_SHADER = + """ uniform shader content; uniform float pixelSize; @@ -33,7 +35,8 @@ class PixelateEffect( float2 p = floor(fragCoord / pixelSize) * pixelSize; return content.eval(p); } - """.trimIndent() + """ + } override fun forFrame(frame: Int): MotionView { if (motionView !is View) return motionView @@ -48,16 +51,17 @@ class PixelateEffect( return motionView } - val pixelSize = MotionInterpolator.interpolateForRange( - interpolator = Interpolators(Easings.LINEAR), - currentFrame = frame, - frameRange = Pair(startFrame, endFrame), - valueRange = Pair(fromPixelSize, toPixelSize), - ) + val pixelSize = + MotionInterpolator.interpolateForRange( + interpolator = Interpolators(Easings.LINEAR), + currentFrame = frame, + frameRange = Pair(startFrame, endFrame), + valueRange = Pair(fromPixelSize, toPixelSize), + ) val shader = RuntimeShader(PIXELATE_SHADER) shader.setFloatUniform("pixelSize", pixelSize) - + view.setRenderEffect(RenderEffect.createRuntimeShaderEffect(shader, "content")) return motionView diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/SepiaEffect.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/SepiaEffect.kt index 510af887..3eb14dd5 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/SepiaEffect.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/SepiaEffect.kt @@ -1,6 +1,5 @@ package com.tejpratapsingh.motionlib.ui.effects -import android.graphics.ColorMatrix import android.graphics.ColorMatrixColorFilter import android.graphics.RenderEffect import android.os.Build @@ -36,33 +35,68 @@ class SepiaEffect( return motionView } - val intensity = MotionInterpolator.interpolateForRange( - interpolator = Interpolators(Easings.LINEAR), - currentFrame = frame, - frameRange = Pair(startFrame, endFrame), - valueRange = Pair(fromIntensity, toIntensity), - ) + val intensity = + MotionInterpolator.interpolateForRange( + interpolator = Interpolators(Easings.LINEAR), + currentFrame = frame, + frameRange = Pair(startFrame, endFrame), + valueRange = Pair(fromIntensity, toIntensity), + ) // Sepia matrix (standard) // R' = (R * .393) + (G * .769) + (B * .189) // G' = (R * .349) + (G * .686) + (B * .168) // B' = (R * .272) + (G * .534) + (B * .131) - - val sepiaMatrix = floatArrayOf( - 0.393f, 0.769f, 0.189f, 0f, 0f, - 0.349f, 0.686f, 0.168f, 0f, 0f, - 0.272f, 0.534f, 0.131f, 0f, 0f, - 0f, 0f, 0f, 1f, 0f - ) - + + val sepiaMatrix = + floatArrayOf( + 0.393f, + 0.769f, + 0.189f, + 0f, + 0f, + 0.349f, + 0.686f, + 0.168f, + 0f, + 0f, + 0.272f, + 0.534f, + 0.131f, + 0f, + 0f, + 0f, + 0f, + 0f, + 1f, + 0f, + ) + // We can interpolate between identity and sepia - val identityMatrix = floatArrayOf( - 1f, 0f, 0f, 0f, 0f, - 0f, 1f, 0f, 0f, 0f, - 0f, 0f, 1f, 0f, 0f, - 0f, 0f, 0f, 1f, 0f - ) - + val identityMatrix = + floatArrayOf( + 1f, + 0f, + 0f, + 0f, + 0f, + 0f, + 1f, + 0f, + 0f, + 0f, + 0f, + 0f, + 1f, + 0f, + 0f, + 0f, + 0f, + 0f, + 1f, + 0f, + ) + val resultMatrix = FloatArray(20) for (i in 0 until 20) { resultMatrix[i] = identityMatrix[i] + (sepiaMatrix[i] - identityMatrix[i]) * intensity diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/VibrateEffect.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/VibrateEffect.kt index 68c48724..2bfef10c 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/VibrateEffect.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/VibrateEffect.kt @@ -21,7 +21,7 @@ class VibrateEffect( // Using sin wave for vibration val offset = sin(frame.toDouble() * frequency).toFloat() * amplitude - + view.translationX = offset view.translationY = offset / 2f // Slight diagonal vibration diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/VintageEffect.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/VintageEffect.kt index 3d6223ec..af0a1f0f 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/VintageEffect.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/ui/effects/VintageEffect.kt @@ -64,19 +64,51 @@ class VintageEffect( // Sepia matrix (standard) val sepiaMatrix = floatArrayOf( - 0.393f, 0.769f, 0.189f, 0f, 0f, - 0.349f, 0.686f, 0.168f, 0f, 0f, - 0.272f, 0.534f, 0.131f, 0f, 0f, - 0f, 0f, 0f, 1f, 0f, + 0.393f, + 0.769f, + 0.189f, + 0f, + 0f, + 0.349f, + 0.686f, + 0.168f, + 0f, + 0f, + 0.272f, + 0.534f, + 0.131f, + 0f, + 0f, + 0f, + 0f, + 0f, + 1f, + 0f, ) // We can interpolate between identity and sepia val identityMatrix = floatArrayOf( - 1f, 0f, 0f, 0f, 0f, - 0f, 1f, 0f, 0f, 0f, - 0f, 0f, 1f, 0f, 0f, - 0f, 0f, 0f, 1f, 0f, + 1f, + 0f, + 0f, + 0f, + 0f, + 0f, + 1f, + 0f, + 0f, + 0f, + 0f, + 0f, + 1f, + 0f, + 0f, + 0f, + 0f, + 0f, + 1f, + 0f, ) val resultMatrix = FloatArray(20) diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/utils/ImageUtil.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/utils/ImageUtil.kt index 2bc18bf5..db944832 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/utils/ImageUtil.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/utils/ImageUtil.kt @@ -14,39 +14,48 @@ import java.io.InputStream object ImageUtil { private val client = OkHttpClient() - suspend fun fetchBitmap(context: Context, uri: Uri): Bitmap? = withContext(Dispatchers.IO) { - val bitmap = when (uri.scheme) { - "http", "https" -> fetchFromNetwork(uri.toString()) - "content", "file", "android.resource" -> fetchFromLocal(context, uri) - else -> null + suspend fun fetchBitmap( + context: Context, + uri: Uri, + ): Bitmap? = + withContext(Dispatchers.IO) { + val bitmap = + when (uri.scheme) { + "http", "https" -> fetchFromNetwork(uri.toString()) + "content", "file", "android.resource" -> fetchFromLocal(context, uri) + else -> null + } + return@withContext bitmap ?: fetchDefault(context) } - return@withContext bitmap ?: fetchDefault(context) - } - private fun fetchDefault(context: Context): Bitmap? { - return try { + private fun fetchDefault(context: Context): Bitmap? = + try { BitmapFactory.decodeResource(context.resources, R.drawable.default_bg) } catch (e: Exception) { null } - } - private fun fetchFromNetwork(url: String): Bitmap? { - return try { - if (url.isBlank()) return null - val request = Request.Builder().url(url).build() - client.newCall(request).execute().use { response -> - if (!response.isSuccessful) return null - val bytes = response.body()?.bytes() ?: return null - BitmapFactory.decodeByteArray(bytes, 0, bytes.size) + private fun fetchFromNetwork(url: String): Bitmap? = + try { + if (url.isBlank()) { + null + } else { + val request = Request.Builder().url(url).build() + client.newCall(request).execute().use { response -> + if (!response.isSuccessful) return null + val bytes = response.body()?.bytes() ?: return null + BitmapFactory.decodeByteArray(bytes, 0, bytes.size) + } } } catch (e: Exception) { null } - } - private fun fetchFromLocal(context: Context, uri: Uri): Bitmap? { - return try { + private fun fetchFromLocal( + context: Context, + uri: Uri, + ): Bitmap? = + try { val inputStream: InputStream? = context.contentResolver.openInputStream(uri) inputStream.use { BitmapFactory.decodeStream(it) @@ -54,5 +63,4 @@ object ImageUtil { } catch (e: Exception) { null } - } } diff --git a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/worker/MotionWorker.kt b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/worker/MotionWorker.kt index 76b5cb98..74a1796f 100644 --- a/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/worker/MotionWorker.kt +++ b/modules/motionlib/src/main/java/com/tejpratapsingh/motionlib/worker/MotionWorker.kt @@ -45,7 +45,7 @@ abstract class MotionWorker( override suspend fun doWork(): Result { Timber.d("Worker ${this.id}: Starting video generation.") - wakeLock.acquire(1 * 60 * 60 * 1000L /* 1 hours */) + wakeLock.acquire(1 * 60 * 60 * 1000L) // 1 hour return try { val videoFile: File = generateVideo( diff --git a/modules/motionlib/src/test/java/com/tejpratapsingh/motionlib/core/motion/MotionTransitionTest.kt b/modules/motionlib/src/test/java/com/tejpratapsingh/motionlib/core/motion/MotionTransitionTest.kt index dee8947c..2500a1cc 100644 --- a/modules/motionlib/src/test/java/com/tejpratapsingh/motionlib/core/motion/MotionTransitionTest.kt +++ b/modules/motionlib/src/test/java/com/tejpratapsingh/motionlib/core/motion/MotionTransitionTest.kt @@ -12,10 +12,9 @@ import org.junit.Assert.assertTrue import org.junit.Test class MotionTransitionTest { - class MockMotionView( override val startFrame: Int, - override val endFrame: Int + override val endFrame: Int, ) : MotionView { override var loop: Pair = Pair(0, 0) override val effects: MutableList = mutableListOf() @@ -25,9 +24,7 @@ class MotionTransitionTest { effects.add(effect) } - override fun getViewBitmap(): Bitmap { - throw UnsupportedOperationException() - } + override fun getViewBitmap(): Bitmap = throw UnsupportedOperationException() override fun forFrame(frame: Int): MotionView = this } @@ -36,29 +33,29 @@ class MotionTransitionTest { fun testCrossFadeTransitionOverlap() { val view1 = MockMotionView(0, 100) val view2 = MockMotionView(101, 200) - + val transition = CrossFadeTransition() val duration = 20 - + transition.apply(view1, view2, duration) - + // startFrame and endFrame should NOT be adjusted assertEquals(0, view1.startFrame) assertEquals(100, view1.endFrame) assertEquals(101, view2.startFrame) assertEquals(200, view2.endFrame) - + // Effects should be added assertTrue(view1.effects.any { it is FadeOutEffect }) assertTrue(view2.effects.any { it is FadeInEffect }) - + val fadeOut = view1.effects.first { it is FadeOutEffect } val fadeIn = view2.effects.first { it is FadeInEffect } - + // Transition centered at boundary 101: [101-10, 101+10-1] = [91, 110] assertEquals(91, fadeOut.startFrame) assertEquals(110, fadeOut.endFrame) - + assertEquals(91, fadeIn.startFrame) assertEquals(110, fadeIn.endFrame) } diff --git a/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/infra/MotionSDUIParsers.kt b/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/infra/MotionSDUIParsers.kt index 5ada249e..7ac1bd39 100644 --- a/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/infra/MotionSDUIParsers.kt +++ b/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/infra/MotionSDUIParsers.kt @@ -89,10 +89,9 @@ fun JsonObject.getMotionPlugins(context: Context): List { /** * Get [MotionConfig] from [JsonObject]. */ -fun JsonObject.getMotionConfig(): MotionConfig? { - return if (has("config") && get("config").isJsonObject) { +fun JsonObject.getMotionConfig(): MotionConfig? = + if (has("config") && get("config").isJsonObject) { get("config").asJsonObject.toMotionConfig() } else { null } -} diff --git a/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/infra/MotionSdui.kt b/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/infra/MotionSdui.kt index 90204b4d..24e4efa6 100644 --- a/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/infra/MotionSdui.kt +++ b/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/infra/MotionSdui.kt @@ -19,7 +19,8 @@ object MotionSdui { private val effectSerializers = mutableMapOf, MotionEffectSerializer>() private val transitionFactories = mutableMapOf() - private val transitionSerializers = mutableMapOf, MotionTransitionSerializer>() + private val transitionSerializers = + mutableMapOf, MotionTransitionSerializer>() private val pluginFactories = mutableMapOf() private val pluginSerializers = mutableMapOf, MotionPluginSerializer>() @@ -30,70 +31,100 @@ object MotionSdui { /** * Register a [MotionView] for deserialization. */ - fun registerView(type: String, factory: MotionViewFactory) { + fun registerView( + type: String, + factory: MotionViewFactory, + ) { viewFactories[type] = factory } /** * Register a [MotionView] for serialization. */ - fun registerViewSerializer(clazz: Class, serializer: MotionViewSerializer) { + fun registerViewSerializer( + clazz: Class, + serializer: MotionViewSerializer, + ) { viewSerializers[clazz] = serializer } /** * Register a [MotionEffect] for deserialization. */ - fun registerEffect(type: String, factory: MotionEffectFactory) { + fun registerEffect( + type: String, + factory: MotionEffectFactory, + ) { effectFactories[type] = factory } /** * Register a [MotionEffect] for serialization. */ - fun registerEffectSerializer(clazz: Class, serializer: MotionEffectSerializer) { + fun registerEffectSerializer( + clazz: Class, + serializer: MotionEffectSerializer, + ) { effectSerializers[clazz] = serializer } /** * Register a [MotionTransition] for deserialization. */ - fun registerTransition(type: String, factory: MotionTransitionFactory) { + fun registerTransition( + type: String, + factory: MotionTransitionFactory, + ) { transitionFactories[type] = factory } /** * Register a [MotionTransition] for serialization. */ - fun registerTransitionSerializer(clazz: Class, serializer: MotionTransitionSerializer) { + fun registerTransitionSerializer( + clazz: Class, + serializer: MotionTransitionSerializer, + ) { transitionSerializers[clazz] = serializer } /** * Register a [MotionPlugin] for deserialization. */ - fun registerPlugin(type: String, factory: MotionPluginFactory) { + fun registerPlugin( + type: String, + factory: MotionPluginFactory, + ) { pluginFactories[type] = factory } /** * Register a [MotionPlugin] for serialization. */ - fun registerPluginSerializer(clazz: Class, serializer: MotionPluginSerializer) { + fun registerPluginSerializer( + clazz: Class, + serializer: MotionPluginSerializer, + ) { pluginSerializers[clazz] = serializer } /** * Register a [MotionAudio] for deserialization. */ - fun registerAudio(type: String, factory: MotionAudioFactory) { + fun registerAudio( + type: String, + factory: MotionAudioFactory, + ) { audioFactories[type] = factory } /** * Register a [MotionAudio] for serialization. */ - fun registerAudioSerializer(clazz: Class, serializer: MotionAudioSerializer) { + fun registerAudioSerializer( + clazz: Class, + serializer: MotionAudioSerializer, + ) { audioSerializers[clazz] = serializer } @@ -129,11 +160,17 @@ object MotionSdui { } fun interface MotionViewFactory { - fun create(context: Context, json: JsonObject): MotionView + fun create( + context: Context, + json: JsonObject, + ): MotionView } fun interface MotionViewSerializer { - fun serialize(view: T, json: JsonObject) + fun serialize( + view: T, + json: JsonObject, + ) } fun interface MotionEffectFactory { @@ -141,7 +178,10 @@ fun interface MotionEffectFactory { } fun interface MotionEffectSerializer { - fun serialize(effect: T, json: JsonObject) + fun serialize( + effect: T, + json: JsonObject, + ) } fun interface MotionTransitionFactory { @@ -149,21 +189,36 @@ fun interface MotionTransitionFactory { } fun interface MotionTransitionSerializer { - fun serialize(transition: T, json: JsonObject) + fun serialize( + transition: T, + json: JsonObject, + ) } fun interface MotionPluginFactory { - fun create(context: Context, json: JsonObject): MotionPlugin + fun create( + context: Context, + json: JsonObject, + ): MotionPlugin } fun interface MotionPluginSerializer { - fun serialize(plugin: T, json: JsonObject) + fun serialize( + plugin: T, + json: JsonObject, + ) } fun interface MotionAudioFactory { - fun create(context: Context, json: JsonObject): MotionAudio + fun create( + context: Context, + json: JsonObject, + ): MotionAudio } fun interface MotionAudioSerializer { - fun serialize(audio: T, json: JsonObject) + fun serialize( + audio: T, + json: JsonObject, + ) } diff --git a/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/infra/MotionTransitionParser.kt b/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/infra/MotionTransitionParser.kt index bbb69afd..70534609 100644 --- a/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/infra/MotionTransitionParser.kt +++ b/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/infra/MotionTransitionParser.kt @@ -21,8 +21,12 @@ fun MotionTransition.toJson(): JsonObject { * Polymorphic deserialization for [MotionTransition]. */ fun JsonObject.toMotionTransition(): MotionTransition { - val type = get("type")?.asString ?: throw IllegalArgumentException("Missing 'type' in MotionTransition JSON") - val factory = MotionSdui.getTransitionFactory(type) ?: throw IllegalArgumentException("No factory registered for MotionTransition type: $type") + val type = + get("type")?.asString + ?: throw IllegalArgumentException("Missing 'type' in MotionTransition JSON") + val factory = + MotionSdui.getTransitionFactory(type) + ?: throw IllegalArgumentException("No factory registered for MotionTransition type: $type") return factory.create(this) } diff --git a/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/infra/VideoAspectRatioParser.kt b/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/infra/VideoAspectRatioParser.kt index db0ff06b..2f07e228 100644 --- a/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/infra/VideoAspectRatioParser.kt +++ b/modules/sdui/src/main/java/com/tejpratapsingh/motion/sdui/infra/VideoAspectRatioParser.kt @@ -62,6 +62,4 @@ private val gsonDefault = Gson() fun VideoAspectRatio.toJson(): JsonObject = gsonDefault.toJsonTree(this).asJsonObject -fun JsonObject.toVideoAspectRatio(): VideoAspectRatio { - return gsonWithAspectRatio.fromJson(this, VideoAspectRatio::class.java) -} +fun JsonObject.toVideoAspectRatio(): VideoAspectRatio = gsonWithAspectRatio.fromJson(this, VideoAspectRatio::class.java) diff --git a/modules/templates/src/androidTest/java/com/tejpratapsingh/motionlib/templates/MotionTemplateInstrumentationTest.kt b/modules/templates/src/androidTest/java/com/tejpratapsingh/motionlib/templates/MotionTemplateInstrumentationTest.kt index 437bc69d..b62ec16f 100644 --- a/modules/templates/src/androidTest/java/com/tejpratapsingh/motionlib/templates/MotionTemplateInstrumentationTest.kt +++ b/modules/templates/src/androidTest/java/com/tejpratapsingh/motionlib/templates/MotionTemplateInstrumentationTest.kt @@ -18,79 +18,87 @@ import java.io.File @RunWith(AndroidJUnit4::class) class MotionTemplateInstrumentationTest { - - data class LyricLine(val text: String, val startFrame: Int, val endFrame: Int) + data class LyricLine( + val text: String, + val startFrame: Int, + val endFrame: Int, + ) @Test fun testHardExampleWithRealContext() { val appContext: Context = InstrumentationRegistry.getInstrumentation().targetContext - + // We need a real MotionVideoProducer for the content block to execute addView/audio calls val producer = MotionVideoProducer.with(appContext) - val lyricsVideoTemplate = motionTemplate("Lyrics Master") { - parameters { - string("songTitle") - string("backgroundVideoPath") - } + val lyricsVideoTemplate = + motionTemplate("Lyrics Master") { + parameters { + string("songTitle") + string("backgroundVideoPath") + } - content { - val bgVideoPath = data.getString("backgroundVideoPath") - assertNotNull(bgVideoPath) - - // Add a dummy background video view using the DSL extension - // Even if the path is fake, we're testing the DSL registration - videoFrameView( - videoUri = android.net.Uri.parse(bgVideoPath!!), - startFrame = 0, - endFrame = 1000 - ) - - // Fetch list of lyrics from data - val lyrics = data.get>("lyricLines") ?: emptyList() - assertEquals(2, lyrics.size) - - lyrics.forEach { line -> - // Use the DSL to add text views for lyrics - popUpTextView( - text = line.text, - startFrame = line.startFrame, - endFrame = line.endFrame + content { + val bgVideoPath = data.getString("backgroundVideoPath") + assertNotNull(bgVideoPath) + + // Add a dummy background video view using the DSL extension + // Even if the path is fake, we're testing the DSL registration + videoFrameView( + videoUri = android.net.Uri.parse(bgVideoPath!!), + startFrame = 0, + endFrame = 1000, ) - } - // Overlay Song Title - val songTitle = data.getString("songTitle") - assertEquals("Amazing Grace", songTitle) - - typeWriterTextView( - text = songTitle!!, - startFrame = 0, - endFrame = 60 - ) - - // Test audio function - audio(File(appContext.cacheDir, "test_audio.mp3"), startFrame = 0, endFrame = 500) + // Fetch list of lyrics from data + val lyrics = data.get>("lyricLines") ?: emptyList() + assertEquals(2, lyrics.size) + + lyrics.forEach { line -> + // Use the DSL to add text views for lyrics + popUpTextView( + text = line.text, + startFrame = line.startFrame, + endFrame = line.endFrame, + ) + } + + // Overlay Song Title + val songTitle = data.getString("songTitle") + assertEquals("Amazing Grace", songTitle) + + typeWriterTextView( + text = songTitle!!, + startFrame = 0, + endFrame = 60, + ) + + // Test audio function + audio(File(appContext.cacheDir, "test_audio.mp3"), startFrame = 0, endFrame = 500) + } } - } assertEquals("Lyrics Master", lyricsVideoTemplate.name) // Mock data - val lyricsData = listOf( - LyricLine("Amazing grace how sweet the sound", 0, 100), - LyricLine("That saved a wretch like me", 101, 200) - ) - - val data = TemplateData(mapOf( - "songTitle" to "Amazing Grace", - "backgroundVideoPath" to "/path/to/video.mp4", - "lyricLines" to lyricsData - )) + val lyricsData = + listOf( + LyricLine("Amazing grace how sweet the sound", 0, 100), + LyricLine("That saved a wretch like me", 101, 200), + ) + + val data = + TemplateData( + mapOf( + "songTitle" to "Amazing Grace", + "backgroundVideoPath" to "/path/to/video.mp4", + "lyricLines" to lyricsData, + ), + ) // Create the scope with REAL context and producer val scope = ContentScope(appContext, producer, data) - + // This will now execute the content block, including DSL extension calls lyricsVideoTemplate.buildContent(scope) diff --git a/modules/templates/src/androidTest/java/com/tejpratapsingh/motionlib/templates/sdui/MotionTemplateSDUIProviderTest.kt b/modules/templates/src/androidTest/java/com/tejpratapsingh/motionlib/templates/sdui/MotionTemplateSDUIProviderTest.kt index 88a2e1e0..e9955676 100644 --- a/modules/templates/src/androidTest/java/com/tejpratapsingh/motionlib/templates/sdui/MotionTemplateSDUIProviderTest.kt +++ b/modules/templates/src/androidTest/java/com/tejpratapsingh/motionlib/templates/sdui/MotionTemplateSDUIProviderTest.kt @@ -15,35 +15,39 @@ import java.io.File @RunWith(AndroidJUnit4::class) class MotionTemplateSDUIProviderTest { - @Test fun testProvideSDUI() { val appContext: Context = InstrumentationRegistry.getInstrumentation().targetContext - val template = motionTemplate("Test Template") { - parameters { - string("title") - } - content { - val title = data.getString("title") ?: "Default" - popUpTextView( - text = title, - startFrame = 0, - endFrame = 100 - ) - audio(File(context.cacheDir, "test.mp3"), startFrame = 0, endFrame = 100) + val template = + motionTemplate("Test Template") { + parameters { + string("title") + } + content { + val title = data.getString("title") ?: "Default" + popUpTextView( + text = title, + startFrame = 0, + endFrame = 100, + ) + audio(File(context.cacheDir, "test.mp3"), startFrame = 0, endFrame = 100) + } } - } - val data = TemplateData(mapOf( - "title" to "Hello SDUI" - )) + val data = + TemplateData( + mapOf( + "title" to "Hello SDUI", + ), + ) - val sdui = MotionTemplateSDUIProvider.provideSDUI( - context = appContext, - template = template, - data = data - ) + val sdui = + MotionTemplateSDUIProvider.provideSDUI( + context = appContext, + template = template, + data = data, + ) assertNotNull(sdui) assertTrue(sdui.has("views")) @@ -52,10 +56,10 @@ class MotionTemplateSDUIProviderTest { val viewsArray = sdui.getAsJsonArray("views") assertEquals(1, viewsArray.size()) - + val firstView = viewsArray[0].asJsonObject assertTrue(firstView.has("type")) - + val audiosArray = sdui.getAsJsonArray("audios") assertEquals(1, audiosArray.size()) } diff --git a/modules/templates/src/main/java/com/tejpratapsingh/motionlib/templates/json/JsonMotionTemplate.kt b/modules/templates/src/main/java/com/tejpratapsingh/motionlib/templates/json/JsonMotionTemplate.kt index e7783e86..5fb76f95 100644 --- a/modules/templates/src/main/java/com/tejpratapsingh/motionlib/templates/json/JsonMotionTemplate.kt +++ b/modules/templates/src/main/java/com/tejpratapsingh/motionlib/templates/json/JsonMotionTemplate.kt @@ -11,13 +11,12 @@ import com.tejpratapsingh.motionlib.templates.serialization.TemplateSerializatio class JsonMotionTemplate( name: String, parameters: List>, - val rawContent: JsonObject + val rawContent: JsonObject, ) : MotionTemplate(name, parameters) { - override fun buildContent(scope: ContentScope) { // 1. Apply data to content JSON val appliedJson = TemplateSerialization.applyData(rawContent, scope.data).asJsonObject - + // 2. Content can be a single view or a container with children // For simplicity, let's assume it's a list of views under "views" key or a single view if (appliedJson.has("views")) { diff --git a/modules/templates/src/main/java/com/tejpratapsingh/motionlib/templates/model/MotionTemplate.kt b/modules/templates/src/main/java/com/tejpratapsingh/motionlib/templates/model/MotionTemplate.kt index 84174ae4..109625af 100644 --- a/modules/templates/src/main/java/com/tejpratapsingh/motionlib/templates/model/MotionTemplate.kt +++ b/modules/templates/src/main/java/com/tejpratapsingh/motionlib/templates/model/MotionTemplate.kt @@ -4,7 +4,7 @@ import com.tejpratapsingh.motionlib.templates.dsl.ContentScope abstract class MotionTemplate( val name: String, - val parameters: List> + val parameters: List>, ) { abstract fun buildContent(scope: ContentScope) diff --git a/modules/templates/src/main/java/com/tejpratapsingh/motionlib/templates/model/TemplateData.kt b/modules/templates/src/main/java/com/tejpratapsingh/motionlib/templates/model/TemplateData.kt index 18903f50..6c9537dc 100644 --- a/modules/templates/src/main/java/com/tejpratapsingh/motionlib/templates/model/TemplateData.kt +++ b/modules/templates/src/main/java/com/tejpratapsingh/motionlib/templates/model/TemplateData.kt @@ -1,11 +1,16 @@ package com.tejpratapsingh.motionlib.templates.model -class TemplateData(private val values: Map) { +class TemplateData( + private val values: Map, +) { @Suppress("UNCHECKED_CAST") fun get(name: String): T? = values[name] as? T fun getString(name: String): String? = get(name) + fun getInt(name: String): Int? = get(name) + fun getFloat(name: String): Float? = get(name) + fun getBoolean(name: String): Boolean? = get(name) } diff --git a/modules/templates/src/main/java/com/tejpratapsingh/motionlib/templates/model/TemplateParameter.kt b/modules/templates/src/main/java/com/tejpratapsingh/motionlib/templates/model/TemplateParameter.kt index bdb7185d..17ce8453 100644 --- a/modules/templates/src/main/java/com/tejpratapsingh/motionlib/templates/model/TemplateParameter.kt +++ b/modules/templates/src/main/java/com/tejpratapsingh/motionlib/templates/model/TemplateParameter.kt @@ -1,12 +1,18 @@ package com.tejpratapsingh.motionlib.templates.model enum class ParameterType { - STRING, INTEGER, FLOAT, COLOR, BOOLEAN, IMAGE, VIDEO + STRING, + INTEGER, + FLOAT, + COLOR, + BOOLEAN, + IMAGE, + VIDEO, } data class TemplateParameter( val name: String, val type: ParameterType, val defaultValue: T? = null, - val description: String? = null + val description: String? = null, ) diff --git a/modules/templates/src/test/java/com/tejpratapsingh/motionlib/templates/TemplateSerializationTest.kt b/modules/templates/src/test/java/com/tejpratapsingh/motionlib/templates/TemplateSerializationTest.kt index 17419cab..9db99e18 100644 --- a/modules/templates/src/test/java/com/tejpratapsingh/motionlib/templates/TemplateSerializationTest.kt +++ b/modules/templates/src/test/java/com/tejpratapsingh/motionlib/templates/TemplateSerializationTest.kt @@ -12,10 +12,10 @@ import org.junit.Assert.assertTrue import org.junit.Test class TemplateSerializationTest { - @Test fun testPlaceholderReplacement() { - val json = """ + val json = + """ { "type": "PopUpTextView", "text": "{{title}}", @@ -25,17 +25,20 @@ class TemplateSerializationTest { "key": "Value: {{title}}" } } - """.trimIndent() - + """.trimIndent() + val content = JsonParser.parseString(json).asJsonObject - val data = TemplateData(mapOf( - "title" to "Hello World", - "duration" to 300, - "color" to 0xFF00FF - )) - + val data = + TemplateData( + mapOf( + "title" to "Hello World", + "duration" to 300, + "color" to 0xFF00FF, + ), + ) + val applied = TemplateSerialization.applyData(content, data).asJsonObject - + assertEquals("Hello World", applied.get("text").asString) assertEquals(300, applied.get("duration").asInt) assertEquals(0xFF00FF, applied.get("color").asInt) @@ -44,22 +47,24 @@ class TemplateSerializationTest { @Test fun testTemplateSerialization() { - val parameters = listOf( - TemplateParameter("title", ParameterType.STRING, "Default"), - TemplateParameter("duration", ParameterType.INTEGER, 100) - ) - val content = JsonObject().apply { - addProperty("type", "SimpleView") - addProperty("text", "{{title}}") - } - + val parameters = + listOf( + TemplateParameter("title", ParameterType.STRING, "Default"), + TemplateParameter("duration", ParameterType.INTEGER, 100), + ) + val content = + JsonObject().apply { + addProperty("type", "SimpleView") + addProperty("text", "{{title}}") + } + val template = JsonMotionTemplate("MyTemplate", parameters, content) val json = TemplateSerialization.templateToJson(template) - + assertEquals("MyTemplate", json.get("name").asString) assertEquals(2, json.getAsJsonArray("parameters").size()) assertTrue(json.has("content")) - + val restoredTemplate = TemplateSerialization.templateFromJson(json) assertEquals(template.name, restoredTemplate.name) assertEquals(template.parameters.size, restoredTemplate.parameters.size) @@ -68,7 +73,8 @@ class TemplateSerializationTest { @Test fun testArrayReplication() { - val json = """ + val json = + """ { "views": [ { @@ -84,25 +90,35 @@ class TemplateSerializationTest { } ] } - """.trimIndent() - + """.trimIndent() + val content = JsonParser.parseString(json).asJsonObject - val data = TemplateData(mapOf( - "items" to listOf( - mapOf("text" to "Item 1", "frame" to 10), - mapOf("text" to "Item 2", "frame" to 20) + val data = + TemplateData( + mapOf( + "items" to + listOf( + mapOf("text" to "Item 1", "frame" to 10), + mapOf("text" to "Item 2", "frame" to 20), + ), + ), ) - )) - + val applied = TemplateSerialization.applyData(content, data).asJsonObject val views = applied.getAsJsonArray("views") - + assertEquals(3, views.size()) // 1 static + 2 dynamic - assertEquals("StaticView", views.get(0).asJsonObject.get("type").asString) - assertEquals("DynamicView", views.get(1).asJsonObject.get("type").asString) - assertEquals("Item 1", views.get(1).asJsonObject.get("text").asString) - assertEquals(10, views.get(1).asJsonObject.get("frame").asInt) - assertEquals("Item 2", views.get(2).asJsonObject.get("text").asString) - assertEquals(20, views.get(2).asJsonObject.get("frame").asInt) + + val view0 = views.get(0).asJsonObject + assertEquals("StaticView", view0.get("type").asString) + + val view1 = views.get(1).asJsonObject + assertEquals("DynamicView", view1.get("type").asString) + assertEquals("Item 1", view1.get("text").asString) + assertEquals(10, view1.get("frame").asInt) + + val view2 = views.get(2).asJsonObject + assertEquals("Item 2", view2.get("text").asString) + assertEquals(20, view2.get("frame").asInt) } }