diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index d03e510..ccd954d 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -8,6 +8,7 @@ ktorClientCoreVersion = "3.3.3"
ktx_version = "1.17.0"
appcompat_version = "1.7.1"
material_version = "1.13.0"
+material_icons = "1.7.8"
lifecycle_runtime_ktx = "2.10.0"
picassoVersion = "2.71828"
smartExceptionJavaVersion = "0.2.1"
@@ -29,6 +30,9 @@ composeBomVersion = "2025.12.00"
navigationComposeVersion = "2.9.6"
ktlint = "14.0.1"
runtimeVersion = "1.10.0"
+room_version = "2.8.4"
+kspversion = "2.2.21-2.0.4"
+glide = "5.0.5"
[libraries]
androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayoutVersion" }
@@ -40,7 +44,7 @@ filament-android = { module = "com.google.android.filament:filament-android", ve
filament-utils-android = { module = "com.google.android.filament:filament-utils-android", version.ref = "filamentAndroidVersion" }
gltfio-android = { module = "com.google.android.filament:gltfio-android", version.ref = "filamentAndroidVersion" }
google-android-material = { group = "com.google.android.material", name = "material", version.ref = "material_version" }
-
+material-icons = {group = "androidx.compose.material", name="material-icons-core", version.ref = "material_icons"}
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle_runtime_ktx" }
androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "work_version" }
@@ -56,6 +60,7 @@ ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negoti
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktorClientCoreVersion" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktorClientCoreVersion" }
picasso = { module = "com.squareup.picasso:picasso", version.ref = "picassoVersion" }
+glide = {module="com.github.bumptech.glide:glide", version.ref="glide" }
smart-exception-java = { module = "com.arthenica:smart-exception-java", version.ref = "smartExceptionJavaVersion" }
tensorflow-lite = { module = "org.tensorflow:tensorflow-lite", version.ref = "tensorflowLiteVersion" }
tensorflow-lite-gpu-delegate-plugin = { module = "org.tensorflow:tensorflow-lite-gpu-delegate-plugin", version.ref = "tensorflowLiteTaskVisionVersion" }
@@ -78,6 +83,12 @@ androidx-material3 = { group = "androidx.compose.material3", name = "material3"
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationComposeVersion" }
androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime", version.ref = "runtimeVersion" }
+#Room
+androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room_version" }
+androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room_version" }
+androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room_version" }
+androidx-room-testing = { group = "androidx.room", name = "room-testing", version.ref = "room_version" }
+
[plugins]
android-application = { id = "com.android.application", version.ref = "agpVersion" }
android-library = { id = "com.android.library", version.ref = "agpVersion" }
@@ -86,6 +97,7 @@ kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", versi
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlinVersion" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlinVersion" }
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" }
+ksp = { id = "com.google.devtools.ksp", version.ref="kspversion"}
[bundles]
androidx = [
diff --git a/modules/app/build.gradle b/modules/app/build.gradle
index 4699f6b..00cbee0 100644
--- a/modules/app/build.gradle
+++ b/modules/app/build.gradle
@@ -44,6 +44,7 @@ dependencies {
implementation libs.androidx.core.ktx
implementation libs.androidx.appcompat
implementation libs.google.android.material
+ implementation libs.material.icons
implementation libs.androidx.lifecycle.runtime.ktx
implementation libs.androidx.work.runtime.ktx
diff --git a/modules/datastore/.gitignore b/modules/datastore/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/modules/datastore/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/modules/datastore/build.gradle b/modules/datastore/build.gradle
new file mode 100644
index 0000000..ac88866
--- /dev/null
+++ b/modules/datastore/build.gradle
@@ -0,0 +1,50 @@
+plugins {
+ alias(libs.plugins.android.library)
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.kotlin.parcelize)
+ alias(libs.plugins.ksp)
+ id 'maven-publish'
+}
+
+android {
+ namespace 'com.tejpratapsingh.motion.datastore'
+ compileSdk 36
+
+ defaultConfig {
+ minSdk 25
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_11
+ targetCompatibility JavaVersion.VERSION_11
+ }
+ kotlinOptions {
+ jvmTarget = '11'
+ }
+}
+
+afterEvaluate {
+ publishing {
+ publications {
+ release(MavenPublication) {
+ from components.release
+ }
+ }
+ }
+}
+
+dependencies {
+ api libs.androidx.room.runtime
+ ksp libs.androidx.room.compiler
+ api libs.androidx.room.ktx
+ testImplementation libs.androidx.room.testing
+}
\ No newline at end of file
diff --git a/modules/datastore/consumer-rules.pro b/modules/datastore/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/modules/datastore/proguard-rules.pro b/modules/datastore/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/modules/datastore/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/modules/datastore/src/main/AndroidManifest.xml b/modules/datastore/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..568741e
--- /dev/null
+++ b/modules/datastore/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/modules/datastore/src/main/java/com/tejpratapsingh/motion/datastore/data/dao/ProjectDao.kt b/modules/datastore/src/main/java/com/tejpratapsingh/motion/datastore/data/dao/ProjectDao.kt
new file mode 100644
index 0000000..9bba470
--- /dev/null
+++ b/modules/datastore/src/main/java/com/tejpratapsingh/motion/datastore/data/dao/ProjectDao.kt
@@ -0,0 +1,17 @@
+package com.tejpratapsingh.motion.datastore.data.dao
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import com.tejpratapsingh.motion.datastore.data.entity.ProjectEntity
+
+@Dao
+interface ProjectDao {
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insert(projectEntity: ProjectEntity)
+
+ @Query("SELECT * FROM projects")
+ suspend fun getAll(): List
+}
\ No newline at end of file
diff --git a/modules/datastore/src/main/java/com/tejpratapsingh/motion/datastore/data/database/DatabaseProvider.kt b/modules/datastore/src/main/java/com/tejpratapsingh/motion/datastore/data/database/DatabaseProvider.kt
new file mode 100644
index 0000000..eb048c7
--- /dev/null
+++ b/modules/datastore/src/main/java/com/tejpratapsingh/motion/datastore/data/database/DatabaseProvider.kt
@@ -0,0 +1,21 @@
+package com.tejpratapsingh.motion.datastore.data.database
+
+import android.content.Context
+import androidx.room.Room
+
+object DatabaseProvider {
+ @Volatile
+ private var INSTANCE: ProjectDatabase? = null
+
+ fun getDatabase(context: Context): ProjectDatabase {
+ return INSTANCE ?: synchronized(this) {
+ val instance = Room.databaseBuilder(
+ context.applicationContext,
+ ProjectDatabase::class.java,
+ "LyricMaker"
+ ).build()
+ INSTANCE = instance
+ instance
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/datastore/src/main/java/com/tejpratapsingh/motion/datastore/data/database/ProjectDatabase.kt b/modules/datastore/src/main/java/com/tejpratapsingh/motion/datastore/data/database/ProjectDatabase.kt
new file mode 100644
index 0000000..bdf699d
--- /dev/null
+++ b/modules/datastore/src/main/java/com/tejpratapsingh/motion/datastore/data/database/ProjectDatabase.kt
@@ -0,0 +1,11 @@
+package com.tejpratapsingh.motion.datastore.data.database
+
+import androidx.room.Database
+import androidx.room.RoomDatabase
+import com.tejpratapsingh.motion.datastore.data.dao.ProjectDao
+import com.tejpratapsingh.motion.datastore.data.entity.ProjectEntity
+
+@Database(entities = [ProjectEntity::class], version = 1, exportSchema = false)
+abstract class ProjectDatabase : RoomDatabase() {
+ abstract fun projectDao(): ProjectDao
+}
\ No newline at end of file
diff --git a/modules/datastore/src/main/java/com/tejpratapsingh/motion/datastore/data/entity/ProjectEntity.kt b/modules/datastore/src/main/java/com/tejpratapsingh/motion/datastore/data/entity/ProjectEntity.kt
new file mode 100644
index 0000000..09dec26
--- /dev/null
+++ b/modules/datastore/src/main/java/com/tejpratapsingh/motion/datastore/data/entity/ProjectEntity.kt
@@ -0,0 +1,26 @@
+package com.tejpratapsingh.motion.datastore.data.entity
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "projects")
+data class ProjectEntity(
+ @PrimaryKey(autoGenerate = true)
+ val id: Long = 0L,
+ val title: String?,
+ val description: String?,
+ val image: String?,
+ val siteName: String?,
+ val twitterCard: String?,
+ val url: String?,
+ val trackName: String?,
+ val artistName: String?,
+ val albumName: String?,
+ val duration: Float?,
+ val instrumental: Boolean?,
+ val plainLyrics: String?,
+ val syncedLyrics: String?,
+ val totalLyrics: String?,
+ val selectedLyrics: String?,
+ val savedFilePath: String?,
+)
\ No newline at end of file
diff --git a/modules/datastore/src/test/java/com/tejpratapsingh/motion/datastore/ExampleUnitTest.kt b/modules/datastore/src/test/java/com/tejpratapsingh/motion/datastore/ExampleUnitTest.kt
new file mode 100644
index 0000000..9b27e14
--- /dev/null
+++ b/modules/datastore/src/test/java/com/tejpratapsingh/motion/datastore/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package com.tejpratapsingh.motion.metadataextractor
+
+import org.junit.Assert.*
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/modules/imageloader/build.gradle b/modules/imageloader/build.gradle
new file mode 100644
index 0000000..1ace2fa
--- /dev/null
+++ b/modules/imageloader/build.gradle
@@ -0,0 +1,47 @@
+plugins {
+ alias(libs.plugins.android.library)
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.kotlin.parcelize)
+ alias(libs.plugins.ksp)
+ id 'maven-publish'
+}
+
+android {
+ namespace 'com.tejpratapsingh.motion.imageloader'
+ compileSdk 36
+
+ defaultConfig {
+ minSdk 25
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_11
+ targetCompatibility JavaVersion.VERSION_11
+ }
+ kotlinOptions {
+ jvmTarget = '11'
+ }
+}
+
+afterEvaluate {
+ publishing {
+ publications {
+ release(MavenPublication) {
+ from components.release
+ }
+ }
+ }
+}
+
+dependencies {
+ api libs.glide
+}
\ No newline at end of file
diff --git a/modules/imageloader/consumer-rules.pro b/modules/imageloader/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/modules/imageloader/proguard-rules.pro b/modules/imageloader/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/modules/imageloader/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/modules/imageloader/src/main/AndroidManifest.xml b/modules/imageloader/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..568741e
--- /dev/null
+++ b/modules/imageloader/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/modules/imageloader/src/main/java/com/tejpratapsingh/motion/imageloader/ImageLoader.kt b/modules/imageloader/src/main/java/com/tejpratapsingh/motion/imageloader/ImageLoader.kt
new file mode 100644
index 0000000..21af364
--- /dev/null
+++ b/modules/imageloader/src/main/java/com/tejpratapsingh/motion/imageloader/ImageLoader.kt
@@ -0,0 +1,37 @@
+package com.tejpratapsingh.motion.imageloader
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.util.Log
+import com.bumptech.glide.Glide
+import com.bumptech.glide.request.target.CustomTarget
+import com.bumptech.glide.request.transition.Transition
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlin.coroutines.resume
+
+object ImageLoader {
+
+ fun loadImage(context: Context, url: String) =
+ Glide.with(context).load(url)
+
+ suspend fun loadBitmap(context: Context, url: String): Bitmap = suspendCancellableCoroutine { cont ->
+ val target = object : CustomTarget() {
+ override fun onResourceReady(resource: Bitmap, transition: Transition?) {
+ cont.resume(resource)
+ }
+ override fun onLoadCleared(placeholder: Drawable?) {
+ if (!cont.isCompleted) cont.cancel()
+ }
+ }
+
+ Glide.with(context)
+ .asBitmap()
+ .load(url)
+ .into(target)
+
+ cont.invokeOnCancellation {
+ Glide.with(context).clear(target)
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/imageloader/src/test/java/com/tejpratapsingh/motion/imageloader/ExampleUnitTest.kt b/modules/imageloader/src/test/java/com/tejpratapsingh/motion/imageloader/ExampleUnitTest.kt
new file mode 100644
index 0000000..a95256c
--- /dev/null
+++ b/modules/imageloader/src/test/java/com/tejpratapsingh/motion/imageloader/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package com.tejpratapsingh.motion.imageloader
+
+import org.junit.Assert.*
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/modules/lyrics-maker/build.gradle b/modules/lyrics-maker/build.gradle
index de30d60..5ffcbb9 100644
--- a/modules/lyrics-maker/build.gradle
+++ b/modules/lyrics-maker/build.gradle
@@ -54,11 +54,11 @@ dependencies {
implementation libs.ktor.client.content.negotiation
implementation libs.ktor.serialization.kotlinx.json
- implementation libs.picasso
-
implementation project(path: ':modules:motionlib')
implementation project(path: ':modules:metadata-extractor')
implementation project(path: ':modules:ffmpeg-motion-ext')
+ implementation project(path: ':modules:ongoing')
+ implementation project(path: ':modules:imageloader')
implementation libs.androidx.work.runtime.ktx
implementation libs.gson
@@ -66,6 +66,7 @@ dependencies {
implementation platform(libs.androidx.compose.bom)
implementation libs.bundles.compose
implementation libs.androidx.navigation.compose
+ implementation libs.material.icons
testImplementation libs.junit
androidTestImplementation libs.androidx.test.ext.junit
diff --git a/modules/lyrics-maker/src/main/AndroidManifest.xml b/modules/lyrics-maker/src/main/AndroidManifest.xml
index 8cf3c83..8dd1caa 100644
--- a/modules/lyrics-maker/src/main/AndroidManifest.xml
+++ b/modules/lyrics-maker/src/main/AndroidManifest.xml
@@ -7,6 +7,7 @@
-
+
@@ -30,6 +31,17 @@
+
+
+
+
+
+
+
+
diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/LyricMaker.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/LyricMaker.kt
new file mode 100644
index 0000000..ab845eb
--- /dev/null
+++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/LyricMaker.kt
@@ -0,0 +1,11 @@
+package com.tejpratapsingh.lyricsmaker
+
+import android.app.Application
+import com.tejpratapsingh.motion.ongoing.domain.ProjectManager
+
+class LyricMaker : Application() {
+ override fun onCreate() {
+ super.onCreate()
+ ProjectManager.initDataBase(applicationContext)
+ }
+}
\ No newline at end of file
diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/lrc/LrcHelper.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/lrc/LrcHelper.kt
index 803cb0b..62dd1a3 100644
--- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/lrc/LrcHelper.kt
+++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/lrc/LrcHelper.kt
@@ -1,5 +1,7 @@
package com.tejpratapsingh.lyricsmaker.data.lrc
+import android.util.Log
+
object LrcHelper {
fun getSyncedLyrics(
lrcContent: String,
@@ -18,12 +20,14 @@ object LrcHelper {
offsetFrames: Int = 0,
parser: LrcParser = LrcParser(),
): List {
+ Log.d("getSyncedLyricsWithFrameOffset", "lrcContentString: $lrcContent")
val parsedResult = parser.parse(lrcContent)
return parsedResult
.map {
val frame =
((it.time / (1000.0 / fps)).toInt() - offsetFrames).coerceAtLeast(0) // avoid negative frames
+ Log.d("getSyncedLyricsWithFrameOffset", "(frame,text) - ($frame,${it.text})")
SyncedLyricFrame(
frame = frame,
text = it.text,
diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/lrc/SyncedLyricFrame.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/lrc/SyncedLyricFrame.kt
index 4882283..4fe8b59 100644
--- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/lrc/SyncedLyricFrame.kt
+++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/data/lrc/SyncedLyricFrame.kt
@@ -9,4 +9,6 @@ import kotlinx.serialization.Serializable
data class SyncedLyricFrame(
val frame: Int,
val text: String,
-) : Parcelable
+) : Parcelable{
+ fun line() = String.format("%d:%s",frame,text)
+}
diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/activity/DashboardActivity.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/activity/DashboardActivity.kt
new file mode 100644
index 0000000..0a8dc9a
--- /dev/null
+++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/activity/DashboardActivity.kt
@@ -0,0 +1,37 @@
+package com.tejpratapsingh.lyricsmaker.presentation.activity
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.viewModels
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Scaffold
+import androidx.compose.ui.Modifier
+import com.tejpratapsingh.lyricsmaker.presentation.compose.AppNavHost
+import com.tejpratapsingh.lyricsmaker.presentation.ui.theme.AnimatorTheme
+import com.tejpratapsingh.lyricsmaker.presentation.viewmodel.LyricsViewModel
+import kotlin.getValue
+
+class DashboardActivity: ComponentActivity() {
+ private val lyricsViewModel: LyricsViewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+
+ setContent {
+ AnimatorTheme {
+ Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
+ AppNavHost(
+ viewModel = lyricsViewModel,
+ modifier = Modifier.padding(innerPadding),
+ )
+ }
+ }
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/activity/SearchActivity.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/activity/SearchActivity.kt
index dc9e940..936d13d 100644
--- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/activity/SearchActivity.kt
+++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/activity/SearchActivity.kt
@@ -1,6 +1,8 @@
package com.tejpratapsingh.lyricsmaker.presentation.activity
import android.Manifest
+import android.content.Context
+import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
@@ -14,7 +16,9 @@ import androidx.compose.material3.Scaffold
import androidx.compose.ui.Modifier
import androidx.core.app.ActivityCompat
import androidx.lifecycle.lifecycleScope
+import androidx.navigation.compose.rememberNavController
import com.tejpratapsingh.lyricsmaker.presentation.compose.AppNavHost
+import com.tejpratapsingh.lyricsmaker.presentation.compose.Screen
import com.tejpratapsingh.lyricsmaker.presentation.ui.theme.AnimatorTheme
import com.tejpratapsingh.lyricsmaker.presentation.viewmodel.LyricsViewModel
import com.tejpratapsingh.lyricsmaker.presentation.worker.LyricsMotionWorker
@@ -49,6 +53,7 @@ class SearchActivity : ComponentActivity() {
AppNavHost(
viewModel = lyricsViewModel,
modifier = Modifier.padding(innerPadding),
+ startDestination = Screen.Home.route
)
}
}
@@ -62,4 +67,12 @@ class SearchActivity : ComponentActivity() {
}
}
}
+
+ companion object {
+ fun startActivity(context: Context) {
+ context.startActivity(
+ Intent(context, SearchActivity::class.java)
+ )
+ }
+ }
}
diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/AppNavHost.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/AppNavHost.kt
index 06f79de..ccb65ec 100644
--- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/AppNavHost.kt
+++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/AppNavHost.kt
@@ -1,13 +1,19 @@
package com.tejpratapsingh.lyricsmaker.presentation.compose
+import android.net.Uri
+import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
+import com.google.gson.Gson
+import com.tejpratapsingh.lyricsmaker.data.lrc.SyncedLyricFrame
import com.tejpratapsingh.lyricsmaker.domain.ensureArrayList
import com.tejpratapsingh.lyricsmaker.presentation.activity.LyricsActivity
import com.tejpratapsingh.lyricsmaker.presentation.viewmodel.LyricsViewModel
+import com.tejpratapsingh.lyricsmaker.utils.getSyncedLyricFrameList
+import com.tejpratapsingh.motion.ongoing.domain.CurrentProject
sealed class Screen(
val route: String,
@@ -15,29 +21,56 @@ sealed class Screen(
object Home : Screen("home")
object Lyrics : Screen("lyrics")
+ object Dashboard : Screen("dashboard")
+ object ProjectDetails : Screen("projectDetails")
}
@Composable
fun AppNavHost(
viewModel: LyricsViewModel,
modifier: Modifier,
+ startDestination: String = Screen.Dashboard.route
) {
val navController = rememberNavController()
- NavHost(navController = navController, startDestination = Screen.Home.route) {
+ NavHost(navController = navController, startDestination = startDestination) {
composable(route = Screen.Home.route) {
SearchScreen(
viewModel = viewModel,
modifier = modifier,
onLyricsSelected = {
viewModel.selectedLyricResponse = it
- navController.navigate(Screen.Lyrics.route)
+ navController.navigate("${Screen.Lyrics.route}/")
},
)
}
- composable(route = Screen.Lyrics.route) {
+ composable(route = Screen.Dashboard.route) {
+ DashBoardScreen(
+ lyricsViewModel = viewModel,
+ navController
+ )
+ }
+
+ composable(route = Screen.ProjectDetails.route) {
+ ProjectDetails(
+ lyricsViewModel = viewModel,
+ modifier = modifier
+ )
+ }
+
+ composable(route = "${Screen.Lyrics.route}/{lyrics}") { backStackEntry ->
+ val itemsArg = backStackEntry.arguments?.getString("lyrics")
+ var lyrics: List? = null
+ if (!itemsArg.isNullOrEmpty()) {
+ Log.d("ooo", "found lyrics empty")
+ lyrics = itemsArg.split(",").let {
+ Log.d("ooo", "found lyrics $it")
+ getSyncedLyricFrameList(it)
+ }
+ }
SyncedLyricsSelector(
+ lyricsList = lyrics,
viewModel = viewModel,
modifier = modifier,
onSelectionChanged = { selectedLyrics ->
diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/DashBoardScreen.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/DashBoardScreen.kt
new file mode 100644
index 0000000..f6974be
--- /dev/null
+++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/DashBoardScreen.kt
@@ -0,0 +1,170 @@
+package com.tejpratapsingh.lyricsmaker.presentation.compose
+
+import android.graphics.Bitmap
+import android.net.Uri
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.lazy.grid.items
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Create
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.DropdownMenu
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.FloatingActionButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.navigation.NavController
+import com.google.gson.Gson
+import com.tejpratapsingh.lyricsmaker.presentation.activity.SearchActivity
+import com.tejpratapsingh.lyricsmaker.presentation.viewmodel.LyricsViewModel
+import com.tejpratapsingh.motion.imageloader.ImageLoader
+import com.tejpratapsingh.motion.ongoing.domain.CurrentProject
+
+@Composable
+fun DashBoardScreen(lyricsViewModel: LyricsViewModel, navController: NavController) {
+
+ val projects by lyricsViewModel.dashboardData.collectAsState()
+ val context = LocalContext.current
+ var fabExpanded by remember { mutableStateOf(false) }
+
+ LaunchedEffect(Unit) {
+ lyricsViewModel.loadDashboardData()
+ }
+
+ Scaffold(
+ floatingActionButton = {
+ Box {
+ FloatingActionButton(
+ onClick = { fabExpanded = true }
+ ) {
+ Icon(Icons.Default.Add, contentDescription = "Add")
+ }
+
+ DropdownMenu(
+ expanded = fabExpanded,
+ onDismissRequest = { fabExpanded = false }
+ ) {
+ DropdownMenuItem(
+ text = { Text("Create New Project") },
+ onClick = {
+ fabExpanded = false
+ SearchActivity.startActivity(context)
+ },
+ leadingIcon = {
+ Icon(
+ imageVector = Icons.Default.Create,
+ contentDescription = null
+ )
+ }
+ )
+ }
+
+ }
+ }
+ ) { padding ->
+ LazyVerticalGrid(
+ columns = GridCells.Fixed(2),
+ contentPadding = PaddingValues(8.dp),
+ modifier = Modifier.padding(padding)
+ ) {
+ items(projects) { item ->
+ ProjectCard(item, modifier = Modifier.padding(8.dp)) {
+ lyricsViewModel.currentSelectedProject = item
+ navController.navigate(Screen.ProjectDetails.route)
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun ProjectCard(
+ project: CurrentProject,
+ modifier: Modifier = Modifier,
+ onClick: (CurrentProject) -> Unit
+) {
+ var bitmap by remember { mutableStateOf(null) }
+ val context = LocalContext.current
+ LaunchedEffect(project.url) {
+ project.image?.let {
+ bitmap = ImageLoader.loadBitmap(context, it)
+ }
+ }
+
+ Card(
+ modifier = modifier
+ .padding(8.dp)
+ .height(180.dp)
+ .clickable {
+ onClick(project)
+ },
+ shape = RoundedCornerShape(12.dp),
+ elevation = CardDefaults.cardElevation(6.dp)
+ ) {
+ Box(modifier = Modifier.fillMaxSize()) {
+
+ // Background Image
+ bitmap?.let {
+ Image(
+ bitmap = it.asImageBitmap(),
+ contentDescription = project.title,
+ modifier = Modifier.fillMaxSize(),
+ contentScale = ContentScale.Crop
+ )
+ }
+
+ // Gradient overlay (for text readability)
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(
+ Brush.verticalGradient(
+ colors = listOf(
+ Color.Transparent,
+ Color.Black.copy(alpha = 0.7f)
+ )
+ )
+ )
+ )
+
+ Text(
+ text = project.title ?: "",
+ color = Color.White,
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Bold,
+ modifier = Modifier
+ .align(Alignment.BottomStart)
+ .padding(12.dp)
+ )
+ }
+ }
+}
diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/ProjectDetails.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/ProjectDetails.kt
new file mode 100644
index 0000000..43627c7
--- /dev/null
+++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/ProjectDetails.kt
@@ -0,0 +1,154 @@
+package com.tejpratapsingh.lyricsmaker.presentation.compose
+
+import android.graphics.Bitmap
+import android.util.Log
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+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.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+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.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.google.gson.GsonBuilder
+import com.tejpratapsingh.lyricsmaker.data.lrc.SyncedLyricFrame
+import com.tejpratapsingh.lyricsmaker.presentation.viewmodel.LyricsViewModel
+import com.tejpratapsingh.motion.imageloader.BuildConfig
+import com.tejpratapsingh.motion.imageloader.ImageLoader
+import com.tejpratapsingh.motion.ongoing.domain.CurrentProject
+import com.tejpratapsingh.motionlib.core.MotionConfig
+import kotlin.let
+import kotlin.text.ifEmpty
+@Composable
+fun ProjectDetails(
+ lyricsViewModel: LyricsViewModel,
+ modifier: Modifier = Modifier
+) {
+ val context = LocalContext.current
+ var bitmap by remember { mutableStateOf(null) }
+ val project = lyricsViewModel.currentSelectedProject!!
+ val screenHeight = LocalConfiguration.current.screenHeightDp.dp
+
+ LaunchedEffect(project.url) {
+ project.image?.let {
+ bitmap = ImageLoader.loadBitmap(context, it)
+ }
+ }
+
+ LazyColumn(
+ modifier = modifier.fillMaxSize(),
+ contentPadding = PaddingValues(bottom = 16.dp)
+ ) {
+
+ // 🔹 HEADER
+ item {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(screenHeight * 0.20f)
+ ) {
+ bitmap?.let {
+ Image(
+ bitmap = it.asImageBitmap(),
+ contentDescription = project.title,
+ contentScale = ContentScale.Fit,
+ modifier = Modifier.fillMaxSize()
+ )
+ }
+
+ Column(
+ modifier = Modifier
+ .align(Alignment.BottomStart)
+ .background(Color.Black.copy(alpha = 0.4f))
+ .padding(12.dp)
+ ) {
+ Text(
+ text = project.trackName ?: "",
+ color = Color.White,
+ fontWeight = FontWeight.Bold,
+ fontSize = 18.sp
+ )
+ Text(
+ text = project.title ?: "",
+ color = Color.White,
+ fontSize = 16.sp
+ )
+ }
+ }
+ }
+
+ item {
+ Spacer(modifier = Modifier.height(8.dp))
+ }
+
+ // LYRICS
+ itemsIndexed(project.selectedLyrics?.split(",") ?: emptyList()) { _, lyric ->
+ LyricRow(lyric)
+ }
+ }
+}
+@Composable
+fun LyricRow(lyric: String) {
+ Log.d("lyric","$lyric /end")
+ val frame = lyric.substringAfter(":")[0].code
+ val text = lyric.substringAfter(":")[1].toString()
+ val line = SyncedLyricFrame(frame = frame, text)
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 12.dp, vertical = 4.dp)
+ .clip(MaterialTheme.shapes.medium)
+ .padding(12.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ "[${line.frame}]",
+ style = MaterialTheme.typography.labelMedium,
+ modifier = Modifier.width(64.dp)
+ )
+
+ Spacer(Modifier.width(8.dp))
+
+ Text(
+ text = line.text.ifEmpty { "…" },
+ style = MaterialTheme.typography.bodyLarge,
+ modifier = Modifier.weight(1f)
+ )
+
+ Spacer(Modifier.width(8.dp))
+
+ Text(
+ "[${line.frame / MotionConfig.fps} sec]",
+ style = MaterialTheme.typography.labelMedium,
+ modifier = Modifier.width(64.dp)
+ )
+ }
+}
diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/SyncedLyricsSelector.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/SyncedLyricsSelector.kt
index 52962a7..fa863a5 100644
--- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/SyncedLyricsSelector.kt
+++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/compose/SyncedLyricsSelector.kt
@@ -50,6 +50,7 @@ data class RangeSelection(
@Composable
fun SyncedLyricsSelector(
+ lyricsList : List?,
viewModel: LyricsViewModel,
modifier: Modifier = Modifier,
onSelectionChanged: (List) -> Unit = {},
@@ -58,11 +59,15 @@ fun SyncedLyricsSelector(
val listState = rememberLazyListState()
val haptics = LocalHapticFeedback.current
var selection by remember { mutableStateOf(null) }
-
+ val lyrics: List = if (lyricsList.isNullOrEmpty()){
+ viewModel.lyrics
+ }else{
+ lyricsList
+ }
Column(modifier = modifier.fillMaxSize()) {
// Selection summary bar
if (selection != null) {
- val selected = viewModel.lyrics.subList(selection!!.minIndex, selection!!.maxIndex + 1)
+ val selected = lyrics.subList(selection!!.minIndex, selection!!.maxIndex + 1)
Surface(tonalElevation = 2.dp) {
Row(
modifier =
@@ -91,7 +96,7 @@ fun SyncedLyricsSelector(
onSelectionChanged(selected)
}
- if (viewModel.lyrics.isEmpty()) {
+ if (lyrics.isEmpty()) {
Box(modifier = Modifier.fillMaxSize()) {
Text("No Lyrics Selected", modifier = Modifier.align(Alignment.Center))
}
@@ -101,7 +106,7 @@ fun SyncedLyricsSelector(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(vertical = 8.dp),
) {
- itemsIndexed(viewModel.lyrics) { index, line ->
+ itemsIndexed(lyrics) { index, line ->
val isSelected = selection?.contains(index) == true
Row(
diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/view/LyricsContainer.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/view/LyricsContainer.kt
index 135df9f..3ea55d0 100644
--- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/view/LyricsContainer.kt
+++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/view/LyricsContainer.kt
@@ -14,6 +14,7 @@ import com.tejpratapsingh.lyricsmaker.R
import com.tejpratapsingh.lyricsmaker.data.api.client.AlbumArtFetcher
import com.tejpratapsingh.lyricsmaker.data.lrc.LrcHelper
import com.tejpratapsingh.lyricsmaker.data.lrc.SyncedLyricFrame
+import com.tejpratapsingh.motion.imageloader.ImageLoader
import com.tejpratapsingh.motionlib.core.MotionEffect
import com.tejpratapsingh.motionlib.core.MotionView
import com.tejpratapsingh.motionlib.core.animation.Easings
@@ -24,7 +25,12 @@ import com.tejpratapsingh.motionlib.core.extensions.toBitmap
import com.tejpratapsingh.motionlib.core.motion.BaseFrameMotionView
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
+import kotlinx.coroutines.MainScope
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
+import kotlin.coroutines.suspendCoroutine
class LyricsContainer(
context: Context,
@@ -46,6 +52,7 @@ class LyricsContainer(
private val progress: SeekBar
private val fakeChartView: FakeAudioChartView
override val effects: List = emptyList()
+ private val viewScope = MainScope()
init {
super.startFrame = startFrame
@@ -72,13 +79,11 @@ class LyricsContainer(
}
ivAlbumArt.apply {
- runBlocking {
+ viewScope.launch {
if (image != null) {
- val client = HttpClient(CIO)
Log.i(TAG, "Using image from social meta: $image")
- setImageBitmap(client.fetchBitmap(image))
- client.close()
- return@runBlocking
+ ImageLoader.loadImage(context,image).into(this@apply)
+
} else {
Log.i(TAG, "Fetching from musicbrainz")
AlbumArtFetcher
@@ -87,8 +92,9 @@ class LyricsContainer(
songName.split(" - ")[1],
)?.let { url ->
Log.i(TAG, "cover art found: $url")
- setImageBitmap(AlbumArtFetcher.fetchAlbumArtBitmap(url))
- AlbumArtFetcher.close()
+ ImageLoader.loadImage(context,url).into(this@apply)
+// setImageBitmap(AlbumArtFetcher.fetchAlbumArtBitmap(url))
+// AlbumArtFetcher.close()
}
}
}
@@ -130,4 +136,9 @@ class LyricsContainer(
}
override fun getViewBitmap(): Bitmap = this.toBitmap()
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ viewScope.cancel()
+ }
}
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 0d34af2..45d44d5 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
@@ -1,41 +1,69 @@
package com.tejpratapsingh.lyricsmaker.presentation.viewmodel
+import android.util.Log
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
import com.tejpratapsingh.lyricsmaker.data.api.client.LrcLibClient
import com.tejpratapsingh.lyricsmaker.data.api.model.LyricsResponse
import com.tejpratapsingh.lyricsmaker.data.api.model.SearchQuery
import com.tejpratapsingh.lyricsmaker.data.lrc.LrcHelper
import com.tejpratapsingh.lyricsmaker.data.lrc.SyncedLyricFrame
+import com.tejpratapsingh.lyricsmaker.utils.getSyncedLyricFrameStringList
import com.tejpratapsingh.motion.metadataextractor.data.SocialMeta
+import com.tejpratapsingh.motion.ongoing.domain.CurrentProject
+import com.tejpratapsingh.motion.ongoing.domain.ProjectManager
import com.tejpratapsingh.motionlib.core.MotionConfig
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
-open class LyricsViewModel : ViewModel() {
+open class LyricsViewModel(val projectManager: ProjectManager = ProjectManager) : ViewModel() {
val socialMeta = MutableStateFlow(null)
val query = MutableStateFlow("")
val isLoading = MutableStateFlow(false)
private val _lyricsList = MutableStateFlow>(emptyList())
val lyricsList: Flow> = _lyricsList
-
+ private val _dashboardData = MutableStateFlow>(emptyList())
+ val dashboardData: StateFlow> = _dashboardData
private val client = LrcLibClient()
- suspend fun fetchLyrics() {
- isLoading.value = true
- val results =
- client.searchLyrics(SearchQuery(query.value)).filter {
- it.syncedLyrics != null
+ init {
+ viewModelScope.launch {
+ socialMeta.collect { socialMeta ->
+ Log.d("receive socialMeta", "$socialMeta")
+ socialMeta?.let {
+ with(projectManager.songProject) {
+ title = it.title
+ description = it.description
+ image = it.image
+ siteName = it.siteName
+ twitterCard = it.twitterCard
+ url = it.url
+ }
+ Log.d("receive songProject", "${projectManager.songProject}")
+ }
}
- _lyricsList.emit(results)
- isLoading.value = false
+ }
}
- var selectedLyricResponse: LyricsResponse =
- LyricsResponse(
- id = 0,
- trackName = "",
- artistName = "",
- )
+ var selectedLyricResponse: LyricsResponse = LyricsResponse(
+ id = 0,
+ trackName = "",
+ artistName = "",
+ )
+ set(value) {
+ field = value
+ with(projectManager.songProject) {
+ trackName = field.trackName
+ artistName = field.artistName
+ albumName = field.albumName
+ duration = field.duration
+ instrumental = field.instrumental
+ plainLyrics = field.plainLyrics
+ syncedLyrics = field.syncedLyrics
+ }
+ }
val selectedSongName: String
get() = "${selectedLyricResponse.trackName} - ${selectedLyricResponse.artistName}"
@@ -45,7 +73,9 @@ open class LyricsViewModel : ViewModel() {
LrcHelper.getSyncedLyrics(
lrcContent = selectedLyricResponse.getLyrics(),
fps = MotionConfig.fps,
- )
+ ).also {
+ ProjectManager.songProject.totalLyrics = getSyncedLyricFrameStringList(it)
+ }
var selectedLyrics: List = emptyList()
get() {
@@ -58,4 +88,35 @@ open class LyricsViewModel : ViewModel() {
)
}.sortedBy { it.frame }
}
+ set(value) {
+ field = value
+ ProjectManager.songProject.apply {
+ selectedLyrics = field.joinToString(separator = ",") {
+ Log.d("ooo","formatted line ${it.line()}")
+ it.line()
+ }
+ }
+ Log.d("ooo","finalSelected ${ProjectManager.songProject.selectedLyrics}")
+ }
+
+ var currentSelectedProject: CurrentProject?=null
+
+ suspend fun fetchLyrics() {
+ isLoading.value = true
+ val results =
+ client.searchLyrics(SearchQuery(query.value)).filter {
+ it.syncedLyrics != null
+ }
+ _lyricsList.emit(results)
+ isLoading.value = false
+ }
+ fun loadDashboardData() {
+ viewModelScope.launch {
+ projectManager.getAllProjects()?.let {
+ _dashboardData.value = it
+ }.also {
+ Log.d("savedData", "$it")
+ }
+ }
+ }
}
diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/worker/LyricsMotionWorker.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/worker/LyricsMotionWorker.kt
index d76dfef..8e017b2 100644
--- a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/worker/LyricsMotionWorker.kt
+++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/presentation/worker/LyricsMotionWorker.kt
@@ -24,8 +24,13 @@ import androidx.work.WorkerParameters
import com.tejpratapsingh.lyricsmaker.data.lrc.SyncedLyricFrame
import com.tejpratapsingh.lyricsmaker.presentation.motion.getLyricsVideoProducer
import com.tejpratapsingh.lyricsmaker.presentation.notification.NotificationFactory
+import com.tejpratapsingh.motion.ongoing.domain.ProjectManager
import com.tejpratapsingh.motionlib.core.motion.MotionVideoProducer
import com.tejpratapsingh.motionlib.worker.MotionWorker
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import java.io.File
import java.net.URLConnection
@@ -111,7 +116,14 @@ class LyricsMotionWorker(
override fun onCompleted(videoFile: File) {
Log.d(TAG, "onCompleted: Video saved to ${videoFile.absolutePath}")
-
+ CoroutineScope(Dispatchers.IO).launch {
+ with(ProjectManager) {
+ with(songProject) {
+ savedFilePath = videoFile.absolutePath
+ }
+ saveProject()
+ }
+ }
// Cancel the progress notification
notificationManager.cancel(progressNotificationId)
diff --git a/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/utils/ProjectUtils.kt b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/utils/ProjectUtils.kt
new file mode 100644
index 0000000..829b1c4
--- /dev/null
+++ b/modules/lyrics-maker/src/main/java/com/tejpratapsingh/lyricsmaker/utils/ProjectUtils.kt
@@ -0,0 +1,19 @@
+package com.tejpratapsingh.lyricsmaker.utils
+
+import com.tejpratapsingh.lyricsmaker.data.lrc.SyncedLyricFrame
+
+fun getSyncedLyricFrameList(data: List) =
+ if (data.isEmpty()){
+ emptyList()
+ }else{
+ data.map {
+ val (frame, text) = it.split(":")
+ SyncedLyricFrame(
+ frame = frame.toInt(),
+ text = text
+ )
+ }
+ }
+
+fun getSyncedLyricFrameStringList(data: List) =
+ data.joinToString(separator = ",") { it.line() }
\ No newline at end of file
diff --git a/modules/metadata-extractor/build.gradle b/modules/metadata-extractor/build.gradle
index 2ddb1a7..5d82b2e 100644
--- a/modules/metadata-extractor/build.gradle
+++ b/modules/metadata-extractor/build.gradle
@@ -50,6 +50,7 @@ dependencies {
implementation libs.bundles.material
implementation project(path: ':modules:core')
+ implementation project(path: ':modules:imageloader')
implementation libs.bundles.ktor
implementation libs.jsoup
diff --git a/modules/metadata-extractor/src/main/java/com/tejpratapsingh/motion/metadataextractor/presentation/ShareReceiverActivity.kt b/modules/metadata-extractor/src/main/java/com/tejpratapsingh/motion/metadataextractor/presentation/ShareReceiverActivity.kt
index 54eb959..5b56561 100644
--- a/modules/metadata-extractor/src/main/java/com/tejpratapsingh/motion/metadataextractor/presentation/ShareReceiverActivity.kt
+++ b/modules/metadata-extractor/src/main/java/com/tejpratapsingh/motion/metadataextractor/presentation/ShareReceiverActivity.kt
@@ -12,6 +12,7 @@ import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
+import com.tejpratapsingh.motion.imageloader.ImageLoader
import com.tejpratapsingh.motion.metadataextractor.data.MetaDataResult
import com.tejpratapsingh.motion.metadataextractor.data.SocialMeta
import com.tejpratapsingh.motion.metadataextractor.databinding.ActivityShareReceiverBinding
@@ -117,8 +118,7 @@ class ShareReceiverActivity : AppCompatActivity() {
private fun loadImage(url: String) {
lifecycleScope.launch {
- val image = metadataViewModel.downloadImage(url)
- binding.ivImage.setImageBitmap(image)
+ ImageLoader.loadImage(this@ShareReceiverActivity, url).into(binding.ivImage)
}
}
}
diff --git a/modules/ongoing/.gitignore b/modules/ongoing/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/modules/ongoing/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/modules/ongoing/build.gradle b/modules/ongoing/build.gradle
new file mode 100644
index 0000000..d4dc8b5
--- /dev/null
+++ b/modules/ongoing/build.gradle
@@ -0,0 +1,47 @@
+plugins {
+ alias(libs.plugins.android.library)
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.kotlin.parcelize)
+ alias(libs.plugins.ksp)
+ id 'maven-publish'
+}
+
+android {
+ namespace 'com.tejpratapsingh.motion.ongoing'
+ compileSdk 36
+
+ defaultConfig {
+ minSdk 25
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_11
+ targetCompatibility JavaVersion.VERSION_11
+ }
+ kotlinOptions {
+ jvmTarget = '11'
+ }
+}
+
+afterEvaluate {
+ publishing {
+ publications {
+ release(MavenPublication) {
+ from components.release
+ }
+ }
+ }
+}
+
+dependencies {
+ implementation(project(":modules:datastore"))
+}
\ No newline at end of file
diff --git a/modules/ongoing/consumer-rules.pro b/modules/ongoing/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/modules/ongoing/proguard-rules.pro b/modules/ongoing/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/modules/ongoing/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/modules/ongoing/src/main/AndroidManifest.xml b/modules/ongoing/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..568741e
--- /dev/null
+++ b/modules/ongoing/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/modules/ongoing/src/main/java/com/tejpratapsingh/motion/ongoing/domain/CurrentProject.kt b/modules/ongoing/src/main/java/com/tejpratapsingh/motion/ongoing/domain/CurrentProject.kt
new file mode 100644
index 0000000..a93b85f
--- /dev/null
+++ b/modules/ongoing/src/main/java/com/tejpratapsingh/motion/ongoing/domain/CurrentProject.kt
@@ -0,0 +1,26 @@
+package com.tejpratapsingh.motion.ongoing.domain
+
+class CurrentProject(
+ var title: String? = null,
+ var description: String? = null,
+ var image: String? = null,
+ var siteName: String? = null,
+ var twitterCard: String? = null,
+ var url: String? = null,
+ var trackName: String? = null,
+ var artistName: String? = null,
+ var albumName: String? = null,
+ var duration: Float? = null,
+ var instrumental: Boolean? = null,
+ var plainLyrics: String? = null,
+ var syncedLyrics: String? = null,
+ var savedFilePath: String? = null,
+ var totalLyrics: String? = null,
+ var selectedLyrics: String? = null,
+) {
+ override fun toString(): String {
+ return "title:$title, description:$description, image:$image, siteName:$siteName, twitterCard:$twitterCard, url:$url, trackName:$trackName," +
+ "artistName:$artistName, albumName:$albumName, duration:$duration, instrument:$instrumental, plainLyrics:$plainLyrics, syncedLyrics:$syncedLyrics," +
+ "savedFilePath:$savedFilePath, totalLyrics:$totalLyrics, selectedLyrics:$selectedLyrics"
+ }
+}
\ No newline at end of file
diff --git a/modules/ongoing/src/main/java/com/tejpratapsingh/motion/ongoing/domain/ProjectManager.kt b/modules/ongoing/src/main/java/com/tejpratapsingh/motion/ongoing/domain/ProjectManager.kt
new file mode 100644
index 0000000..cf9b9b8
--- /dev/null
+++ b/modules/ongoing/src/main/java/com/tejpratapsingh/motion/ongoing/domain/ProjectManager.kt
@@ -0,0 +1,28 @@
+package com.tejpratapsingh.motion.ongoing.domain
+
+import android.content.Context
+import android.util.Log
+import com.tejpratapsingh.motion.datastore.data.dao.ProjectDao
+import com.tejpratapsingh.motion.datastore.data.database.DatabaseProvider
+import com.tejpratapsingh.motion.datastore.data.database.ProjectDatabase
+import com.tejpratapsingh.motion.ongoing.domain.mapper.toCurrentProject
+import com.tejpratapsingh.motion.ongoing.domain.mapper.toProjectEntity
+
+object ProjectManager {
+
+ private var projectDatabase: ProjectDatabase?=null
+ private var projectDao: ProjectDao?=null
+ var songProject = CurrentProject()
+ private set
+ fun initDataBase(context: Context) {
+ projectDatabase = DatabaseProvider.getDatabase(context)
+ projectDao = projectDatabase?.projectDao()
+ }
+ suspend fun saveProject() {
+ Log.d("savingData","$songProject")
+ projectDao?.insert(songProject.toProjectEntity())
+ }
+ suspend fun getAllProjects(): List? = projectDao?.getAll()?.map {
+ it.toCurrentProject()
+ }
+}
\ No newline at end of file
diff --git a/modules/ongoing/src/main/java/com/tejpratapsingh/motion/ongoing/domain/mapper/Mapper.kt b/modules/ongoing/src/main/java/com/tejpratapsingh/motion/ongoing/domain/mapper/Mapper.kt
new file mode 100644
index 0000000..b4f9cb1
--- /dev/null
+++ b/modules/ongoing/src/main/java/com/tejpratapsingh/motion/ongoing/domain/mapper/Mapper.kt
@@ -0,0 +1,42 @@
+package com.tejpratapsingh.motion.ongoing.domain.mapper
+
+import com.tejpratapsingh.motion.datastore.data.entity.ProjectEntity
+import com.tejpratapsingh.motion.ongoing.domain.CurrentProject
+
+fun CurrentProject.toProjectEntity() = ProjectEntity(
+ title = this.title,
+ description = this.description,
+ image = this.image,
+ siteName = this.siteName,
+ twitterCard = this.twitterCard,
+ url = this.url,
+ trackName = this.trackName,
+ artistName = this.artistName,
+ albumName = this.albumName,
+ duration = this.duration,
+ instrumental = this.instrumental,
+ plainLyrics = this.plainLyrics,
+ syncedLyrics = this.syncedLyrics,
+ savedFilePath = this.savedFilePath,
+ selectedLyrics = this.selectedLyrics,
+ totalLyrics = this.totalLyrics
+)
+
+fun ProjectEntity.toCurrentProject() = CurrentProject(
+ title = this.title,
+ description = this.description,
+ image = this.image,
+ siteName = this.siteName,
+ twitterCard = this.twitterCard,
+ url = this.url,
+ trackName = this.trackName,
+ artistName = this.artistName,
+ albumName = this.albumName,
+ duration = this.duration,
+ instrumental = this.instrumental,
+ plainLyrics = this.plainLyrics,
+ syncedLyrics = this.syncedLyrics,
+ savedFilePath = this.savedFilePath,
+ selectedLyrics = this.selectedLyrics,
+ totalLyrics = this.totalLyrics
+)
\ No newline at end of file
diff --git a/modules/ongoing/src/test/java/com/tejpratapsingh/motion/ongoing/ExampleUnitTest.kt b/modules/ongoing/src/test/java/com/tejpratapsingh/motion/ongoing/ExampleUnitTest.kt
new file mode 100644
index 0000000..72a20ee
--- /dev/null
+++ b/modules/ongoing/src/test/java/com/tejpratapsingh/motion/ongoing/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package com.tejpratapsingh.motion.ongoing
+
+import org.junit.Assert.*
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index ff3f9d8..10177cf 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -31,3 +31,6 @@ include ':modules:ivi-demo'
include ':modules:lyrics-maker'
include ':modules:sdui'
include ':modules:metadata-extractor'
+include ':modules:datastore'
+include ':modules:ongoing'
+include ':modules:imageloader'