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'