Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/generate-apk-aab-debug-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ jobs:
- name: Ensure gradlew is executable
run: chmod +x ./gradlew

- name: Decode google-services.json
run: echo "${{ secrets.GOOGLE_SERVICES_JSON }}" | openssl base64 -d > modules/lyrics-maker/google-services.json

- name: Build APK for module ${{ matrix.module }}
# Using assembleRelease so you get APKs in build/outputs; if you prefer debug builds (always unsigned),
# change to :${{ matrix.module }}:assembleDebug
Expand All @@ -71,7 +74,7 @@ jobs:
shell: bash

- name: Upload APK artifact for ${{ matrix.module }}
if: always() # still run to ensure artifact (even if none found, action will create empty artifact)
if: always() # still run to ensure artifact (even if none found, action will create empty artifact)
uses: actions/upload-artifact@v4
with:
# name artifact per module so each appears separately in Actions UI
Expand Down
34 changes: 32 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
[versions]
firebaseBomVersion = "34.9.0"
supabaseBomVersion = "3.4.0"
constraintlayoutVersion = "2.2.1"
contourVersion = "1.1.0"
filamentAndroidVersion = "1.69.2"
filamentAndroidVersion = "1.69.4"
jcodecVersion = "0.2.5"
jsoupVersion = "1.22.1"
kotlinxCoroutinesCoreVersion = "1.10.2"
Expand All @@ -28,8 +30,10 @@ activityComposeVersion = "1.12.4"
composeBomVersion = "2026.02.00"
navigationComposeVersion = "2.9.7"
ktlint = "14.0.1"
runtimeVersion = "1.10.3"
runtimeVersion = "1.10.4"
composeCompilerVersion = "1.5.14"
crashlyticsVersion = "3.0.6"
googleServicesVersion = "4.4.4"

[libraries]
androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayoutVersion" }
Expand All @@ -39,12 +43,14 @@ androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-view
contour = { module = "app.cash.contour:contour", version.ref = "contourVersion" }
filament-android = { module = "com.google.android.filament:filament-android", version.ref = "filamentAndroidVersion" }
filament-utils-android = { module = "com.google.android.filament:filament-utils-android", version.ref = "filamentAndroidVersion" }
firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics" }
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" }

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" }

google-firebase-analytics = { module = "com.google.firebase:firebase-analytics" }
jcodec = { module = "org.jcodec:jcodec", version.ref = "jcodecVersion" }
jcodec-android = { module = "org.jcodec:jcodec-android", version.ref = "jcodecVersion" }
jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoupVersion" }
Expand Down Expand Up @@ -81,13 +87,25 @@ androidx-navigation-compose = { group = "androidx.navigation", name = "navigatio
androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime", version.ref = "runtimeVersion" }
compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" }

supabase-bom = { module = "io.github.jan-tennert.supabase:bom", version.ref = "supabaseBomVersion" }
supabase-auth-kt = { module = "io.github.jan-tennert.supabase:auth-kt" }
supabase-postgrest-kt = { module = "io.github.jan-tennert.supabase:postgrest-kt" }
supabase-realtime-kt = { module = "io.github.jan-tennert.supabase:realtime-kt" }

firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBomVersion" }
firebase-analytics = { module = "com.google.firebase:firebase-analytics" }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Duplicate firebase-analytics library alias

firebase-analytics (line 96) and google-firebase-analytics (line 53) both resolve to the same artifact com.google.firebase:firebase-analytics. The firebase bundle references google-firebase-analytics, so firebase-analytics is unused and will cause confusion. Remove the duplicate:

Suggested change
firebase-analytics = { module = "com.google.firebase:firebase-analytics" }

Delete line 96 (firebase-analytics = { module = "com.google.firebase:firebase-analytics" }) to avoid the redundant alias.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Duplicate Firebase Analytics library definition

firebase-analytics (line 96) maps to the same Maven artifact com.google.firebase:firebase-analytics as the pre-existing google-firebase-analytics (line 53). The firebase bundle at line 173 references google-firebase-analytics, so firebase-analytics is unused. Having two aliases for the same artifact causes confusion and may lead to duplicate dependency declarations. Remove one of the two definitions and update the bundle reference to use a single consistent alias.

firebase-auth = { module = "com.google.firebase:firebase-auth" }
firebase-firestore = { module = "com.google.firebase:firebase-firestore" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agpVersion" }
android-library = { id = "com.android.library", version.ref = "agpVersion" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinVersion" }
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" }
google-services = { id = "com.google.gms.google-services", version.ref = "googleServicesVersion" }
crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "crashlyticsVersion" }

[bundles]
androidx = [
Expand Down Expand Up @@ -141,4 +159,16 @@ compose = [
"androidx-ui-tooling-preview",
"androidx-material3",
"androidx-compose-runtime"
]
supabase = [
"supabase-auth-kt",
"supabase-postgrest-kt",
"supabase-realtime-kt",
"ktor-client-cio",
]
firebase = [
"firebase-auth",
"firebase-firestore",
"firebase-crashlytics",
"google-firebase-analytics"
]
5 changes: 5 additions & 0 deletions modules/lyrics-maker/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ plugins {
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.kotlin.parcelize)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.google.services)
alias(libs.plugins.crashlytics)
}

android {
Expand Down Expand Up @@ -64,6 +66,9 @@ dependencies {
implementation libs.androidx.navigation.compose
implementation(libs.compose.material.icons.extended)

implementation platform(libs.firebase.bom)
implementation libs.bundles.firebase

testImplementation libs.junit
androidTestImplementation libs.androidx.test.ext.junit
androidTestImplementation libs.androidx.test.espresso.core
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,55 @@ package com.tejpratapsingh.lyricsmaker

import android.app.Application
import android.content.Context
import androidx.work.Configuration
import com.google.firebase.FirebaseApp
import com.tejpratapsingh.motionstore.dao.DownloadedTrackerDao
import com.tejpratapsingh.motionstore.dao.MotionProjectDao
import com.tejpratapsingh.motionstore.infra.DatabaseManager
import com.tejpratapsingh.motionstore.infra.FirebaseAdapter
import com.tejpratapsingh.motionstore.infra.SyncManager
import com.tejpratapsingh.motionstore.worker.SyncWorker
import com.tejpratapsingh.motionstore.worker.SyncWorkerFactory

class LyricsApp : Application() {
class LyricsApp :
Application(),
Configuration.Provider {
val database by lazy { DatabaseManager.init(this) }
val motionStore by lazy { MotionProjectDao(database) }
val motionStoreDao by lazy { MotionProjectDao(database) }

val syncManager by lazy {
SyncManager(
backend = FirebaseAdapter(),
downloadedTracker = DownloadedTrackerDao(database),
)
}

override fun onCreate() {
super.onCreate()

FirebaseApp.initializeApp(this)
SyncWorker.schedulePeriodic(this)
}

/**
* Provides a custom WorkManager configuration injecting [SyncManager]
* and DAOs into [SyncWorker] via [SyncWorkerFactory].
*
* Implementing [Configuration.Provider] here replaces the default
* WorkManager auto-init. Remove the WorkManagerInitializer <meta-data>
* entry from AndroidManifest.xml (see SyncWorker KDoc for the snippet).
*/

override val workManagerConfiguration: Configuration
get() =
Configuration
.Builder()
.setWorkerFactory(
SyncWorkerFactory(
syncManager = syncManager,
daos = listOf(motionStoreDao),
),
).build()
}

fun Context.asLyricsApp(): LyricsApp = applicationContext as LyricsApp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import java.net.URLConnection
class SearchActivity : ComponentActivity() {
private val projectsViewModel: ProjectsViewModel by viewModels {
ProjectsViewModelFactory(
applicationContext.asLyricsApp().motionStore,
applicationContext.asLyricsApp().motionStoreDao,
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.tejpratapsingh.lyricsmaker.presentation.compose

import android.content.Intent
import android.graphics.Bitmap
import android.media.MediaMetadataRetriever
import androidx.compose.foundation.BorderStroke
Expand All @@ -24,6 +23,8 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Add
import androidx.compose.material.icons.rounded.CloudDone
import androidx.compose.material.icons.rounded.CloudOff
import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material.icons.rounded.PlayCircle
import androidx.compose.material.icons.rounded.Share
Expand All @@ -41,7 +42,6 @@ import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
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
Expand Down Expand Up @@ -233,7 +233,8 @@ private fun ProjectCard(
var showDeleteDialog by remember { mutableStateOf(false) }

val context = LocalContext.current
val thumbnail: Bitmap? = remember(project.id) { extractFirstFrame(context.createProjectFile(project).path) }
val thumbnail: Bitmap? =
remember(project.id) { extractFirstFrame(context.createProjectFile(project).path) }

if (showDeleteDialog) {
DeleteConfirmationDialog(
Expand Down Expand Up @@ -286,24 +287,38 @@ private fun ProjectCard(
.padding(14.dp),
verticalArrangement = Arrangement.SpaceBetween,
) {
Box(
modifier =
Modifier
.size(40.dp)
.background(
color = MaterialTheme.colorScheme.secondary.copy(alpha = 0.15f),
shape = RoundedCornerShape(10.dp),
),
contentAlignment = Alignment.Center,
Row(
modifier = Modifier.fillMaxWidth(), // Ensure the row takes up full width
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
// Left Item
Box(
modifier =
Modifier
.size(40.dp)
.background(
color = MaterialTheme.colorScheme.secondary.copy(alpha = 0.15f),
shape = RoundedCornerShape(10.dp),
),
contentAlignment = Alignment.Center,
) {
Icon(
imageVector = Icons.Rounded.PlayCircle,
contentDescription = null,
tint = MaterialTheme.colorScheme.secondary,
modifier = Modifier.size(22.dp),
)
}

// Right Item
Icon(
imageVector = Icons.Rounded.PlayCircle,
imageVector = if (project.syncTracker.isDirty) Icons.Rounded.CloudOff else Icons.Rounded.CloudDone,
contentDescription = null,
tint = MaterialTheme.colorScheme.secondary,
modifier = Modifier.size(22.dp),
)
}

Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text(
text = project.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ class LyricsMotionWorker(

val motionProject = provideCurrentProject()
Log.i(TAG, "onCompleted: $motionProject")
applicationContext.asLyricsApp().motionStore.upsert(motionProject)
applicationContext.asLyricsApp().motionStoreDao.upsert(motionProject)

// Cancel the progress notification
notificationManager.cancel(progressNotificationId)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package com.tejpratapsingh.motion.metadataextractor.presentation

import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.inputmethod.EditorInfo
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.IntentCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
Expand All @@ -26,11 +26,7 @@ class ShareReceiverActivity : AppCompatActivity() {
const val ACTIVITY_INTENT_ACTION = "com.tejpratapsingh.motion.metadataextractor.action.OPEN"

fun readMetadataFromIntent(intent: Intent): SocialMeta? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(EXTRA_METADATA, SocialMeta::class.java)
} else {
intent.getParcelableExtra(EXTRA_METADATA) as SocialMeta?
}
IntentCompat.getParcelableExtra(intent, EXTRA_METADATA, SocialMeta::class.java)
}

private lateinit var binding: ActivityShareReceiverBinding
Expand Down Expand Up @@ -112,8 +108,7 @@ class ShareReceiverActivity : AppCompatActivity() {
Intent(ACTIVITY_INTENT_ACTION).apply {
putExtra(
EXTRA_METADATA,
result.metaData
.copy(title = binding.tvTitle.text.toString()),
result.metaData.copy(title = binding.tvTitle.text.toString()),
)
},
)
Expand All @@ -134,9 +129,7 @@ class ShareReceiverActivity : AppCompatActivity() {

is MetaDataResult.Error -> {
Log.e(TAG, "Received error", result.error)
Toast
.makeText(this, "Failed to fetch metadata", Toast.LENGTH_SHORT)
.show()
Toast.makeText(this, "Failed to fetch metadata", Toast.LENGTH_SHORT).show()
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions modules/motion-store/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ dependencies {
implementation libs.androidx.core.ktx
implementation libs.androidx.appcompat

implementation platform(libs.supabase.bom)
implementation libs.bundles.supabase

implementation platform(libs.firebase.bom)
implementation libs.bundles.firebase

implementation libs.androidx.work.runtime.ktx

implementation project(":modules:core")

testImplementation libs.junit
Expand Down
7 changes: 6 additions & 1 deletion modules/motion-store/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application>
<provider
android:name=".infra.DeviceInfoInitializer"
android:authorities="${applicationId}.device-info-init"
android:exported="false" />
</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,13 @@ abstract class BaseDao<T>(
// ── Read ─────────────────────────────────────────────────────────────────

/** Fetch a single entity by primary key, or null if not found. */
fun findById(id: Long): T? =
fun findById(id: String): T? =
db
.query(
tableName,
null,
"$primaryKey = ?",
arrayOf(id.toString()),
arrayOf(id),
null,
null,
null,
Expand Down Expand Up @@ -150,7 +150,7 @@ abstract class BaseDao<T>(

/** Update the row with [id]. Returns number of rows affected. */
fun update(
id: Long,
id: String,
entity: T,
): Int = db.update(tableName, toContentValues(entity), "$primaryKey = ?", arrayOf(id.toString()))

Expand Down
Loading
Loading