diff --git a/.github/workflows/reusable_build_sample_apps.yml b/.github/workflows/reusable_build_sample_apps.yml index 7fcde66e1..128d8afe9 100644 --- a/.github/workflows/reusable_build_sample_apps.yml +++ b/.github/workflows/reusable_build_sample_apps.yml @@ -22,9 +22,11 @@ jobs: - "kotlin_compose" include: # Add additional variables to each sample app build: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrixinclude - sample-app: "java_layout" + cio-workspace-name: "Mobile: Native iOS & Android" cio-cdpapikey-secret-key: "CUSTOMERIO_JAVA_WORKSPACE_CDP_API_KEY" cio-siteid-secret-key: "CUSTOMERIO_JAVA_WORKSPACE_SITE_ID" - sample-app: "kotlin_compose" + cio-workspace-name: "Mobile: xiOS CocoaPods FCM + Kotlin Android" cio-cdpapikey-secret-key: "CUSTOMERIO_KOTLIN_WORKSPACE_CDP_API_KEY" cio-siteid-secret-key: "CUSTOMERIO_KOTLIN_WORKSPACE_SITE_ID" @@ -91,8 +93,10 @@ jobs: touch "samples/local.properties" echo "cdpApiKey=${{ secrets[matrix.cio-cdpapikey-secret-key] }}" >> "samples/local.properties" echo "siteId=${{ secrets[matrix.cio-siteid-secret-key] }}" >> "samples/local.properties" - echo "branch=$BRANCH_NAME" >> "samples/local.properties" - echo "commit=${COMMIT_HASH:0:7}" >> "samples/local.properties" + echo "workspace=${{ matrix.cio-workspace-name }}" >> "samples/local.properties" + echo "branchName=$BRANCH_NAME" >> "samples/local.properties" + echo "commitHash=${COMMIT_HASH:0:7}" >> "samples/local.properties" + echo "commitsAheadCount=$(git rev-list $(git describe --tags --abbrev=0)..HEAD --count)" >> samples/local.properties if [ "${{ inputs.use_latest_sdk_version == true }}" ]; then echo "sdkVersion=${{ steps.latest-sdk-version-step.outputs.LATEST_TAG }}" >> "samples/local.properties" fi diff --git a/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/buildinfo/BuildInfoMetadata.java b/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/buildinfo/BuildInfoMetadata.java new file mode 100644 index 000000000..808af3bc6 --- /dev/null +++ b/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/buildinfo/BuildInfoMetadata.java @@ -0,0 +1,80 @@ +package io.customer.android.sample.java_layout.buildinfo; + +import android.graphics.Typeface; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.StyleSpan; + +import androidx.annotation.NonNull; + +import java.util.Locale; + +import io.customer.android.sample.java_layout.BuildConfig; +import io.customer.sdk.Version; + +/** + * Contains metadata about the build environment. + */ +public class BuildInfoMetadata { + private final String sdkVersion; + private final String appVersion; + private final String buildDate; + private final String gitMetadata; + private final String defaultWorkspace; + private final String language; + private final String uiFramework; + private final String sdkIntegration; + + public BuildInfoMetadata() { + this.sdkVersion = BuildInfoMetadataUtils.resolveValidOrElse(BuildConfig.SDK_VERSION, () -> String.format( + Locale.ENGLISH, "%s-%s", + Version.version, + BuildInfoMetadataUtils.resolveValidOrElse(BuildConfig.COMMITS_AHEAD_COUNT, () -> "as-source"))); + this.appVersion = String.valueOf(BuildConfig.VERSION_CODE); + this.buildDate = BuildInfoMetadataUtils.formatBuildDateWithRelativeTime(BuildConfig.BUILD_TIMESTAMP); + this.gitMetadata = String.format(Locale.ENGLISH, "%s-%s", + BuildInfoMetadataUtils.resolveValidOrElse(BuildConfig.BRANCH_NAME, () -> "working branch"), + BuildInfoMetadataUtils.resolveValidOrElse(BuildConfig.COMMIT_HASH, () -> "untracked")); + this.defaultWorkspace = BuildInfoMetadataUtils.resolveValidOrElse(BuildConfig.DEFAULT_WORKSPACE); + this.language = BuildInfoMetadataUtils.resolveValidOrElse(BuildConfig.LANGUAGE); + this.uiFramework = BuildInfoMetadataUtils.resolveValidOrElse(BuildConfig.UI_FRAMEWORK); + this.sdkIntegration = BuildInfoMetadataUtils.resolveValidOrElse(BuildConfig.SDK_INTEGRATION); + } + + @NonNull + @Override + public String toString() { + return toFormattedString().toString(); + } + + @NonNull + public CharSequence toFormattedString() { + SpannableStringBuilder builder = new SpannableStringBuilder(); + + appendBold(builder, "SDK Version: "); + builder.append(sdkVersion).append(" \t"); + appendBold(builder, "App version: "); + builder.append(appVersion).append("\n"); + appendBold(builder, "Build Date: "); + builder.append(buildDate).append("\n"); + appendBold(builder, "Branch: "); + builder.append(gitMetadata).append("\n"); + appendBold(builder, "Default Workspace: "); + builder.append(defaultWorkspace).append("\n"); + appendBold(builder, "Language: "); + builder.append(language).append(" \t"); + appendBold(builder, "UI Framework: "); + builder.append(uiFramework).append("\n"); + appendBold(builder, "SDK Integration: "); + builder.append(sdkIntegration); + + return builder; + } + + private void appendBold(@NonNull SpannableStringBuilder builder, @NonNull String text) { + SpannableString spannable = new SpannableString(text); + spannable.setSpan(new StyleSpan(Typeface.BOLD), 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + builder.append(spannable); + } +} diff --git a/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/buildinfo/BuildInfoMetadataUtils.java b/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/buildinfo/BuildInfoMetadataUtils.java new file mode 100644 index 000000000..6744b941d --- /dev/null +++ b/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/buildinfo/BuildInfoMetadataUtils.java @@ -0,0 +1,46 @@ +package io.customer.android.sample.java_layout.buildinfo; + +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.text.DateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.concurrent.TimeUnit; + +public class BuildInfoMetadataUtils { + public static String resolveValidOrElse(@Nullable String text) { + return resolveValidOrElse(text, () -> "unknown"); + } + + public static String resolveValidOrElse(@Nullable String text, @NonNull ValueProvider fallbackProvider) { + // When local properties are not set, they have a string value of "null" + if (!TextUtils.isEmpty(text) && !"null".equalsIgnoreCase(text)) { + return text; + } + + return fallbackProvider.get(); + } + + public static String formatBuildDateWithRelativeTime(long buildTimestamp) { + Date buildDate = new Date(buildTimestamp); + String formattedDate = DateFormat.getDateTimeInstance().format(buildDate); + + long diffInMillis = System.currentTimeMillis() - buildTimestamp; + long daysAgo = TimeUnit.MILLISECONDS.toDays(diffInMillis); + String relativeTime = daysAgo == 0 ? "(Today)" : String.format(Locale.ENGLISH, "(%d days ago)", daysAgo); + + return String.format(Locale.ENGLISH, "%s %s", formattedDate, relativeTime); + } + + /** + * Provides a value when the original value is not valid using lambda expressions. + * This can be simplified with Java 8+ or Kotlin later if needed. + */ + public interface ValueProvider { + @NonNull + T get(); + } +} diff --git a/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/utils/ViewUtils.java b/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/utils/ViewUtils.java index c98fc1beb..fc505e289 100644 --- a/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/utils/ViewUtils.java +++ b/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/utils/ViewUtils.java @@ -15,12 +15,8 @@ import com.google.android.material.textfield.TextInputEditText; import com.google.android.material.textfield.TextInputLayout; -import java.text.DateFormat; -import java.util.Date; -import java.util.Locale; - -import io.customer.android.sample.java_layout.BuildConfig; import io.customer.android.sample.java_layout.R; +import io.customer.android.sample.java_layout.buildinfo.BuildInfoMetadata; public class ViewUtils { public static void prepareForAutomatedTests(@NonNull View view, @StringRes int contentDescResId) { @@ -55,38 +51,8 @@ public static void setError(@NonNull TextInputLayout textInputLayout, @Nullable } public static void setBuildInfo(@NonNull TextView textView) { - String buildInfo = String.format( - Locale.ENGLISH, - "SDK version: %s\n" + - "Build date: %s\n" + - "Branch: %s\n" + - "Default workspace: Native iOS & Android\n" + - "App version: %s", - getSdkVersion(), - getBuildTime(), - getBranchName(), - BuildConfig.VERSION_CODE - ); - textView.setText(buildInfo); - } - - private static String getBuildTime() { - return DateFormat.getDateTimeInstance().format(new Date(BuildConfig.BUILD_TIMESTAMP)); - } - - private static String getSdkVersion() { - if (isEmptyOrUnset(BuildConfig.SDK_VERSION)) return "as source code"; - return BuildConfig.SDK_VERSION; - } - - private static String getBranchName() { - if (isEmptyOrUnset(BuildConfig.BRANCH)) return "local development"; - return BuildConfig.BRANCH + "." + BuildConfig.COMMIT; - } - - private static boolean isEmptyOrUnset(String text) { - // When local properties are not set, they have a string value of "null" - return TextUtils.isEmpty(text) || "null".equalsIgnoreCase(text); + BuildInfoMetadata buildInfo = new BuildInfoMetadata(); + textView.setText(buildInfo.toFormattedString()); } @NonNull diff --git a/samples/sample-app.gradle b/samples/sample-app.gradle index b424a3c53..2d2951441 100644 --- a/samples/sample-app.gradle +++ b/samples/sample-app.gradle @@ -13,6 +13,25 @@ android { Closure getConfigWithPrefix = { String key -> localProperties."${configKeyPrefix}${key}" ?: localProperties."${key}" } + // Helper methods to retrieve language and UI framework from prefix + Closure getLanguageFromPrefix = { String prefix -> + String normalizedPrefix = prefix.toLowerCase() + if (normalizedPrefix.startsWith("java")) { + return "Java" + } else if (normalizedPrefix.startsWith("kotlin")) { + return "Kotlin" + } + return null + } + Closure getUIFrameworkFromPrefix = { String prefix -> + String normalizedPrefix = prefix.toLowerCase() + if (normalizedPrefix.contains("layout")) { + return "Android XML" + } else if (normalizedPrefix.contains("compose")) { + return "Jetpack Compose" + } + return null + } // Retrieve API keys using the helper method to allow key prefixing // e.g. Java Layout sample app has prefix javaLayout_ for API keys @@ -20,16 +39,26 @@ android { // cdpApiKey=KEY can be used as a fallback for all sample apps String cdpApiKey = getConfigWithPrefix("cdpApiKey") String siteId = getConfigWithPrefix("siteId") + String workspace = getConfigWithPrefix("workspace") + String language = getConfigWithPrefix("language") ?: getLanguageFromPrefix(configKeyPrefix) + String uiFramework = getConfigWithPrefix("uiFramework") ?: getUIFrameworkFromPrefix(configKeyPrefix) + String sdkIntegration = "Maven" String sdkVersion = localProperties["sdkVersion"] - String branch = localProperties["branch"] - String commit = localProperties["commit"] + String branchName = localProperties["branchName"] + String commitHash = localProperties["commitHash"] + String commitsAheadCount = localProperties["commitsAheadCount"] // Set build config fields for API keys buildConfigField "String", "CDP_API_KEY", "\"${cdpApiKey}\"" buildConfigField "String", "SITE_ID", "\"${siteId}\"" + buildConfigField "String", "DEFAULT_WORKSPACE", "\"${workspace}\"" + buildConfigField "String", "LANGUAGE", "\"${language}\"" + buildConfigField "String", "UI_FRAMEWORK", "\"${uiFramework}\"" + buildConfigField "String", "SDK_INTEGRATION", "\"${sdkIntegration}\"" buildConfigField "String", "SDK_VERSION", "\"${sdkVersion}\"" - buildConfigField "String", "BRANCH", "\"${branch}\"" - buildConfigField "String", "COMMIT", "\"${commit}\"" + buildConfigField "String", "BRANCH_NAME", "\"${branchName}\"" + buildConfigField "String", "COMMIT_HASH", "\"${commitHash}\"" + buildConfigField "String", "COMMITS_AHEAD_COUNT", "\"${commitsAheadCount}\"" buildConfigField "long", "BUILD_TIMESTAMP", System.currentTimeMillis() + "L" } // Avoid redefining signing configs in sample apps to avoid breaking release