Skip to content
Merged
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
4 changes: 2 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ComposeAndroidTemplate">
android:theme="@style/Theme.ComposeAndroidTemplate.Starting">
<activity
android:name=".app.MainActivity"
android:exported="true"
android:theme="@style/Theme.ComposeAndroidTemplate">
android:theme="@style/Theme.ComposeAndroidTemplate.Starting">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.codermp.composeandroidtemplate.app.navigation
import androidx.compose.runtime.Composable
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable

/**
* Composable function that defines the root navigation graph for the application.
Expand All @@ -19,5 +20,6 @@ fun NavigationRoot(
/**
* Define your navigation graphs &/or composable routes here.
*/
composable<Routes.Home> { }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,6 @@ private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40

/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)

@Composable
Expand Down
11 changes: 11 additions & 0 deletions app/src/main/res/values/splash.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.ComposeAndroidTemplate.Starting" parent="Theme.SplashScreen">
<item name="android:actionBarStyle">@style/Theme.AppCompat.NoActionBar</item>
Comment thread
CoderMP marked this conversation as resolved.
<!-- TODO: Add your windowSplashScreenBackground color property-->
Comment thread
CoderMP marked this conversation as resolved.
<!-- TODO: Add your windowSplashScreenAnimatedIcon drawable property-->
<item name="windowSplashScreenBackground">@color/purple_700</item>
Copy link

Copilot AI Aug 6, 2025

Choose a reason for hiding this comment

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

The splash screen is referencing @color/purple_700 which may not be defined in the project's color resources. This could cause compilation errors or runtime crashes if the color resource is missing.

Copilot uses AI. Check for mistakes.
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_launcher_foreground</item>
Copy link

Copilot AI Aug 6, 2025

Choose a reason for hiding this comment

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

The splash screen is referencing @drawable/ic_launcher_foreground which may not exist in all template configurations. This could cause runtime crashes if the drawable is missing. Consider using a more reliable default or adding validation.

Suggested change
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_launcher_foreground</item>
<item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item>

Copilot uses AI. Check for mistakes.
<item name="postSplashScreenTheme">@style/Theme.ComposeAndroidTemplate</item>
</style>
</resources>
1 change: 0 additions & 1 deletion app/src/main/res/values/themes.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>

<style name="Theme.ComposeAndroidTemplate" parent="android:Theme.Material.Light.NoActionBar" />
</resources>
162 changes: 128 additions & 34 deletions buildSrc/src/main/kotlin/cleanup.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ tasks.register("templateCleanup") {
it[0].sanitized() to it[1].sanitized()
}

val nameCasePreserved = repository.split("/")[1].sanitizedPreservingCase()

file(path = "gradle/libs.versions.toml").replace(
oldValue = "com.codermp.composeandroidtemplate",
newValue = "com.$owner.$name"
Expand All @@ -31,7 +33,10 @@ tasks.register("templateCleanup") {
)

changePackageName(owner = owner, name = name)
changeManifestFile(name = name)
changeManifestFile(name = nameCasePreserved)
changeAppClassFile(name = nameCasePreserved)
changeThemeFile(name = nameCasePreserved)
changeResourceFiles(name = nameCasePreserved)

// cleanup the cleanup :)
file(path = ".github/workflows/cleanup.yml").delete()
Expand All @@ -43,18 +48,43 @@ tasks.register("templateCleanup") {
}
}

/**
* [String] extension function that sanitizes a string by removing all non-alphanumeric characters and
* converting it to lowercase.
*/
fun String.sanitized() = replace(
regex = Regex(pattern = "[^A-Za-z0-9]"),
replacement = ""
).lowercase()

/**
* [String] extension function that sanitizes a string by removing all non-alphanumeric characters
* while preserving the case. This is useful for cases where the original case of the string is
* important, such as in class names.
*/
fun String.sanitizedPreservingCase() = split(regex = Regex("[^A-Za-z0-9]"))
.filter { it.isNotEmpty() }
.joinToString("")

/**
* [File] extension function that replaces all occurrences of `oldValue` with `newValue` in the file.
* @param oldValue The string to be replaced.
* @param newValue The string to replace with.
*/
fun File.replace(oldValue: String, newValue: String) {
writeText(text = readText().replace(oldValue, newValue))
}

fun srcDirectories() = projectDir.listFiles()!!
/**
* Returns a list of source directories in the project.
* It excludes the `buildSrc` directory to avoid unnecessary processing.
*/
fun srcDirectories() = projectDir
.listFiles()!!
.filter { it.isDirectory && it.name != "buildSrc" }
.flatMap { it.listFiles()!!.filter { it.isDirectory && it.name == "src" } }
.flatMap {
it.listFiles()!!.filter { it.isDirectory && it.name == "src" }
}

@Suppress("NestedBlockDepth")
/**
Expand All @@ -63,12 +93,18 @@ fun srcDirectories() = projectDir.listFiles()!!
* @param name The name of the repository, usually the project name.
*/
fun changePackageName(owner: String, name: String) {
srcDirectories().forEach {
it.walk().filter {
it.isFile && (it.extension == "kt" || it.extension == "kts" || it.extension == "xml")
}.forEach {
it.replace("com.codermp.composeandroidtemplate", "com.$owner.$name")
}
srcDirectories().forEach { directory ->
directory
.walk()
.filter {
it.isFile && (it.extension == "kt" || it.extension == "kts" || it.extension == "xml")
}
.forEach { file ->
file.replace(
oldValue = "com.codermp.composeandroidtemplate",
newValue = "com.$owner.$name"
)
}
}
srcDirectories().forEach { srcDir ->
srcDir
Expand Down Expand Up @@ -112,42 +148,100 @@ fun changePackageName(owner: String, name: String) {
* @param name The name of the repository, usually the project name.
*/
fun changeManifestFile(name: String) {
val capitalizedName = name.split("-", "_")
.joinToString("") { it.replaceFirstChar { char -> char.uppercaseChar() } }

// Update AndroidManifest.xml files
projectDir.walk()
// Update AndroidManifest.xml file
projectDir
.walk()
.filter { it.name == "AndroidManifest.xml" }
.forEach { manifestFile ->
manifestFile.replace(
oldValue = "android:name=\".app.TemplateApp\"",
newValue = "android:name=\".app.${capitalizedName}App\""
newValue = "android:name=\".app.${name}App\""
)
manifestFile.replace(
oldValue = "android:theme=\"@style/Theme.ComposeAndroidTemplate.Starting\"",
newValue = "android:theme=\"@style/Theme.$name.Starting\"",
)
}
}

// Update strings.xml files
projectDir.walk()
/**
* Change the main application class file to reflect the new project name.
* @param name The name of the repository, usually the project name.
*/
fun changeAppClassFile(name: String) {
// Rename TemplateApp class file
srcDirectories()
.forEach { srcDir ->
srcDir
.walk()
.filter { it.isFile && it.name == "TemplateApp.kt" }
.forEach { templateAppFile ->
// Update class name inside the file
templateAppFile.replace(
oldValue = "class TemplateApp", newValue = "class ${name}App"
)
templateAppFile.replace(
oldValue = "this@TemplateApp", newValue = "this@${name}App"
)

// Rename the file itself
val newFile = File(templateAppFile.parent, "${name}App.kt")
templateAppFile.renameTo(newFile)
}
}
}

/**
* Change the Compose theme file to reflect the new project name.
* @param name The name of the repository, usually the project name.
*/
fun changeThemeFile(name: String) {
srcDirectories()
.forEach { srcDir ->
srcDir
.walk()
.filter { it.isFile && it.name == "Theme.kt" }
.forEach { themeFile ->
themeFile.replace(
oldValue = "ComposeAndroidTemplateTheme", newValue = "${name}Theme"
)
}
}
}

/**
* Change the resource files such as themes.xml, strings.xml, and splash.xml.
* @param name The name of the repository, usually the project name.
*/
fun changeResourceFiles(name: String) {
// Update themes.xml file
projectDir
.walk()
.filter { it.name == "themes.xml" }
.forEach { themesFile ->
themesFile.replace(
oldValue = "Theme.ComposeAndroidTemplate", newValue = "Theme.$name"
)
}

// Update strings.xml file
projectDir
.walk()
.filter { it.name == "strings.xml" }
.forEach { stringsFile ->
stringsFile.replace(
oldValue = "Compose Android Template",
newValue = name.split("-", "_")
.joinToString(" ") { it.replaceFirstChar { char -> char.uppercaseChar() } }
oldValue = "Compose Android Template", newValue = name
Comment thread
CoderMP marked this conversation as resolved.
Comment thread
CoderMP marked this conversation as resolved.
Copy link

Copilot AI Aug 6, 2025

Choose a reason for hiding this comment

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

[nitpick] The replacement uses the raw name parameter which may not be user-friendly for display purposes. Consider using a formatted version that converts kebab-case or snake_case to proper title case for better readability in the app name.

Copilot uses AI. Check for mistakes.
)
}

// Rename TemplateApp class files
srcDirectories().forEach { srcDir ->
srcDir.walk()
.filter { it.isFile && it.name == "TemplateApp.kt" }
.forEach { templateAppFile ->
// Update class name inside the file
templateAppFile.replace("class TemplateApp", "class ${capitalizedName}App")
templateAppFile.replace("this@TemplateApp", "this@${capitalizedName}App")

// Rename the file itself
val newFile = File(templateAppFile.parent, "${capitalizedName}App.kt")
templateAppFile.renameTo(newFile)
}
}
// Update splash.xml file
projectDir
.walk()
.filter { it.name == "splash.xml" }
.forEach { splashFile ->
splashFile.replace(
oldValue = "Theme.ComposeAndroidTemplate.Starting",
newValue = "Theme.${name}.Starting"
)
}
}
Loading