Skip to content
Draft
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
115 changes: 66 additions & 49 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ plugins {
}

android {
namespace = "com.babelsoftware.airnote"
compileSdk = 36
namespace = "com.babelsoftware.loudly"
compileSdk = 35
flavorDimensions += "default"

buildFeatures {
Expand All @@ -19,28 +19,21 @@ android {
productFlavors {
create("default") {
dimension = "default"
applicationId = "com.babelsoftware.airnote"
applicationId = "com.babelsoftware.loudly"
versionNameSuffix = "-default"
}

create("accrescent") {
dimension = "default"
applicationId = "by.babelapps.airnote"
versionNameSuffix = "-babelapps"
}
}

defaultConfig {
applicationId = "com.babelsoftware.airnote"
applicationId = "com.babelsoftware.loudly"
minSdk = 26
targetSdk = 36
targetSdk = 35
versionCode = 1
versionName = "v1.6.1"
vectorDrawables {
useSupportLibrary = true
}

// https://developer.android.com/guide/topics/resources/app-languages#gradle-config
resourceConfigurations.plus(
listOf("en", "ar", "de", "es", "fa", "fil", "fr", "hi", "it", "ja", "ru", "sk", "tr", "da", "nl", "pl", "tr", "uk", "vi", "ota", "pt-rBR", "sr", "zh-rCN")
)
Expand Down Expand Up @@ -84,46 +77,70 @@ android {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
buildToolsVersion = "36.0.0"
buildToolsVersion = "35.0.0"
}

dependencies {
implementation(libs.androidx.biometric.ktx)
implementation(libs.androidx.glance)
implementation(libs.coil.compose)
implementation(libs.hilt.navigation.compose)
implementation(libs.androidx.glance.appwidget)
implementation(libs.androidx.material3.window.size.class1.android)
ksp(libs.androidx.room.compiler)
ksp(libs.hilt.android.compiler)
ksp(libs.hilt.compile)

implementation("com.google.ai.client.generativeai:generativeai:0.9.0")
implementation("androidx.security:security-crypto:1.1.0")
implementation("com.udojava:EvalEx:2.7")
implementation("androidx.activity:activity-compose:1.11.0")
implementation("com.google.mlkit:translate:17.0.3")
implementation("com.google.mlkit:language-id:17.0.6")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.10.2")
implementation("org.jsoup:jsoup:1.21.2")
implementation("com.google.code.gson:gson:2.13.2")
implementation(platform("io.ktor:ktor-bom:3.0.0-beta-2"))
implementation("io.ktor:ktor-client-core")
implementation("io.ktor:ktor-client-android")
implementation("io.ktor:ktor-client-content-negotiation")
implementation("io.ktor:ktor-client-logging")
implementation("io.ktor:ktor-serialization-kotlinx-json")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0")
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.hilt.navigation.compose)

implementation(libs.hilt.android)
implementation(libs.androidx.datastore.preferences)
// Room
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.compose.material.icons.extended)
implementation(libs.androidx.room.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.material3)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.core.splashscreen)
implementation(libs.androidx.navigation.compose)
}
ksp(libs.androidx.room.compiler)

// Hilt
implementation(libs.hilt.android)
ksp(libs.hilt.android.compiler)
ksp(libs.hilt.compiler)

// Coil
implementation(libs.coil.compose)
implementation(libs.coil.gif)

// Media3
implementation(libs.androidx.media3.exoplayer)
implementation(libs.androidx.media3.session)
implementation(libs.androidx.media3.ui)
implementation(libs.androidx.media3.common)

// Palette
implementation(libs.androidx.palette.ktx)

// JSON
implementation("com.google.code.gson:gson:2.10.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")

// Coroutines
implementation(libs.kotlinx.coroutines.android)

// Network (Retrofit/OkHttp assumed if not using Ktor, but saw Ktor in AirNote. Loudly might differ. Sticking to generics)
// Actually the imports showed org.json but not retrofit explicitly in the snippets.
// I will add standard networking just in case
implementation("com.squareup.okhttp3:okhttp:4.12.0")

// Image Cropper (from manifest)
implementation("com.vanniktech:android-image-cropper:4.5.0")

// Material 3 Window Size Class
implementation("androidx.compose.material3:material3-window-size-class:1.2.1")

// Squiggles
implementation("me.saket.squiggles:squiggles:0.2.0")

testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
}
4 changes: 4 additions & 0 deletions app/src/debug/res/values/app_name.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">Loudly Debug</string>
</resources>
43 changes: 43 additions & 0 deletions app/src/debug/res/xml-v25/shortcuts.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:shortcutId="songs"
android:enabled="true"
android:icon="@drawable/shortcut_songs"
android:shortcutShortLabel="@string/songs">
<intent
android:targetClass="com.babelsoftware.loudly.MainActivity"
android:targetPackage="com.babelsoftware.loudly"
android:action="com.babelsoftware.loudly.action.SONGS" />
</shortcut>
<shortcut
android:shortcutId="artists"
android:enabled="true"
android:icon="@drawable/shortcut_artists"
android:shortcutShortLabel="@string/artists">
<intent
android:targetClass="com.babelsoftware.loudly.MainActivity"
android:targetPackage="com.babelsoftware.loudly"
android:action="com.babelsoftware.loudly.action.ARTISTS" />
</shortcut>
<shortcut
android:shortcutId="albums"
android:enabled="true"
android:icon="@drawable/shortcut_albums"
android:shortcutShortLabel="@string/albums">
<intent
android:targetClass="com.babelsoftware.loudly.MainActivity"
android:targetPackage="com.babelsoftware.loudly"
android:action="com.babelsoftware.loudly.action.ALBUMS" />
</shortcut>
<shortcut
android:shortcutId="playlists"
android:enabled="true"
android:icon="@drawable/shortcut_playlists"
android:shortcutShortLabel="@string/filter_playlists">
<intent
android:targetClass="com.babelsoftware.loudly.MainActivity"
android:targetPackage="com.babelsoftware.loudly"
android:action="com.babelsoftware.loudly.action.PLAYLISTS" />
</shortcut>
</shortcuts>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.babelsoftware.loudly

import com.babelsoftware.loudly.db.entities.LyricsEntity

object TranslationHelper {
fun translate(lyrics: LyricsEntity): LyricsEntity = lyrics
fun clearModels() {}
}
5 changes: 5 additions & 0 deletions app/src/foss/java/com/babelsoftware/loudly/Utils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.babelsoftware.loudly

fun reportException(throwable: Throwable) {
throwable.printStackTrace()
}
78 changes: 78 additions & 0 deletions app/src/full/java/com/babelsoftware/loudly/TranslationHelper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.babelsoftware.loudly

import android.util.LruCache
import com.babelsoftware.loudly.db.entities.LyricsEntity
import com.babelsoftware.loudly.lyrics.LyricsUtils
import com.github.houbb.opencc4j.util.ZhConverterUtil
import com.google.mlkit.common.model.DownloadConditions
import com.google.mlkit.common.model.RemoteModelManager
import com.google.mlkit.nl.languageid.LanguageIdentification
import com.google.mlkit.nl.translate.TranslateLanguage
import com.google.mlkit.nl.translate.TranslateRemoteModel
import com.google.mlkit.nl.translate.Translation
import com.google.mlkit.nl.translate.TranslatorOptions
import kotlinx.coroutines.tasks.await
import java.util.Locale

object TranslationHelper {
private const val MAX_CACHE_SIZE = 20
private val cache = LruCache<String, LyricsEntity>(MAX_CACHE_SIZE)

suspend fun translate(lyrics: LyricsEntity): LyricsEntity {
cache[lyrics.id]?.let { return it }
val isSynced = lyrics.lyrics.startsWith("[")
val sourceLanguage = TranslateLanguage.fromLanguageTag(
LanguageIdentification.getClient().identifyLanguage(
lyrics.lyrics.lines().joinToString(separator = "\n") { it.replace("\\[\\d{2}:\\d{2}.\\d{2,3}\\] *".toRegex(), "") }
).await()
)
val targetLanguage = TranslateLanguage.fromLanguageTag(
Locale.getDefault().toLanguageTag().substring(0..1)
)
return if (sourceLanguage == null || targetLanguage == null || sourceLanguage == targetLanguage) {
lyrics
} else {
val translator = Translation.getClient(
TranslatorOptions.Builder()
.setSourceLanguage(sourceLanguage)
.setTargetLanguage(targetLanguage)
.build()
)
translator.downloadModelIfNeeded(
DownloadConditions.Builder()
.requireWifi()
.build()
).await()
val traditionalChinese = Locale.getDefault().toLanguageTag().replace("-Hant", "") == "zh-TW"
lyrics.copy(
lyrics = if (isSynced) {
LyricsUtils.parseLyrics(lyrics.lyrics, trim = true, multilineEnable = false).map {
val translated = translator.translate(it.text).await()
it.copy(
text = if (traditionalChinese) ZhConverterUtil.toTraditional(translated) else translated
)
}.joinToString(separator = "\n") {
"[%02d:%02d.%03d]${it.text}".format(it.time / 60000, (it.time / 1000) % 60, it.time % 1000)
}
} else {
lyrics.lyrics.lines()
.map {
val translated = translator.translate(it).await()
if (traditionalChinese) ZhConverterUtil.toTraditional(translated) else translated
}
.joinToString(separator = "\n")
}
)
}.also {
cache.put(it.id, it)
}
}

suspend fun clearModels() {
val modelManager = RemoteModelManager.getInstance()
val downloadedModels = modelManager.getDownloadedModels(TranslateRemoteModel::class.java).await()
downloadedModels.forEach {
modelManager.deleteDownloadedModel(it).await()
}
}
}
9 changes: 9 additions & 0 deletions app/src/full/java/com/babelsoftware/loudly/Utils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.babelsoftware.loudly

import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase

fun reportException(throwable: Throwable) {
Firebase.crashlytics.recordException(throwable)
throwable.printStackTrace()
}
Loading