From 600aa41b1f13bac27678d7b680c18742e109b327 Mon Sep 17 00:00:00 2001 From: Xisheng-Zhao Date: Wed, 3 Jun 2026 02:15:20 +0800 Subject: [PATCH] Prepare Multi-Casual public analytics release --- app/build.gradle | 32 +- app/proguard-rules.pro | 2 + .../ai/multica/app/AnalyticsConsentStore.java | 40 +++ .../java/ai/multica/app/AppAnalytics.java | 277 ++++++++++++++++++ .../ai/multica/app/ComposePilotActivity.kt | 42 ++- .../java/ai/multica/app/MainActivity.java | 6 +- .../ai/multica/app/MulticaApplication.java | 1 + .../ai/multica/app/OnboardingPayload.java | 6 +- .../app/ui/components/MulticaComponents.kt | 6 +- app/src/main/res/values/strings.xml | 2 +- 10 files changed, 390 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/ai/multica/app/AnalyticsConsentStore.java create mode 100644 app/src/main/java/ai/multica/app/AppAnalytics.java diff --git a/app/build.gradle b/app/build.gradle index 96f25d6..dde870b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,6 +4,18 @@ plugins { id "org.jetbrains.kotlin.plugin.compose" } +def umengPublicAppKey = providers.gradleProperty("multicaUmengPublicAppKey") + .orElse("") + .get() +def postHogPublicApiKey = providers.gradleProperty("multicaPostHogPublicApiKey") + .orElse("") + .get() +def postHogPublicHost = providers.gradleProperty("multicaPostHogPublicHost") + .orElse("https://us.i.posthog.com") + .get() +def analyticsRoute = providers.gradleProperty("multicaAnalyticsRoute") + .orElse("auto") + .get() android { namespace "ai.multica.app" @@ -11,7 +23,7 @@ android { buildToolsVersion "36.1.0" defaultConfig { - applicationId "ai.multica.app" + applicationId "ai.multicasual.app" minSdk 24 targetSdk 36 versionCode 1 @@ -37,14 +49,21 @@ android { productFlavors { create("public") { dimension "distribution" - applicationId "ai.multica.app" - resValue "string", "app_name", "Multica" - manifestPlaceholders = [usesCleartextTraffic: "false", deepLinkScheme: "multica"] + applicationId "ai.multicasual.app" + resValue "string", "app_name", "Multi-Casual" + manifestPlaceholders = [usesCleartextTraffic: "false", deepLinkScheme: "multi-casual"] buildConfigField "String", "MULTICA_ENVIRONMENT", "\"public\"" buildConfigField "String", "MULTICA_API_BASE_URL", "\"https://api.multica.ai\"" buildConfigField "String", "MULTICA_WEB_BASE_URL", "\"https://app.multica.ai\"" buildConfigField "String", "MULTICA_WS_BASE_URL", "\"wss://api.multica.ai\"" - buildConfigField "String", "MULTICA_AUTH_PREFS_NAME", "\"multica_auth\"" + buildConfigField "String", "MULTICA_AUTH_PREFS_NAME", "\"multi_casual_auth\"" + buildConfigField "String", "ANALYTICS_ROUTE", "\"${analyticsRoute}\"" + buildConfigField "boolean", "UMENG_ENABLED", "true" + buildConfigField "String", "UMENG_APP_KEY", "\"${umengPublicAppKey}\"" + buildConfigField "String", "UMENG_CHANNEL", "\"public\"" + buildConfigField "boolean", "POSTHOG_ENABLED", "true" + buildConfigField "String", "POSTHOG_API_KEY", "\"${postHogPublicApiKey}\"" + buildConfigField "String", "POSTHOG_HOST", "\"${postHogPublicHost}\"" } } @@ -56,6 +75,9 @@ android { } dependencies { + publicImplementation "com.umeng.umsdk:common:9.9.2" + publicImplementation "com.umeng.umsdk:asms:1.8.7.2" + publicImplementation "com.posthog:posthog-android:3.47.0" implementation platform("androidx.compose:compose-bom:2024.10.01") implementation "androidx.activity:activity-compose:1.9.3" implementation "androidx.compose.foundation:foundation" diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index e69de29..930ec23 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -0,0 +1,2 @@ +-keep class com.umeng.** { *; } +-keep class com.posthog.** { *; } diff --git a/app/src/main/java/ai/multica/app/AnalyticsConsentStore.java b/app/src/main/java/ai/multica/app/AnalyticsConsentStore.java new file mode 100644 index 0000000..1b97cf9 --- /dev/null +++ b/app/src/main/java/ai/multica/app/AnalyticsConsentStore.java @@ -0,0 +1,40 @@ +package ai.multica.app; + +import android.content.Context; +import android.content.SharedPreferences; + +final class AnalyticsConsentStore { + private static final String PREFS_SUFFIX = "_analytics"; + private static final String CONSENT_DECIDED = "analytics_consent_decided"; + private static final String CONSENT_GRANTED = "analytics_consent_granted"; + + private final SharedPreferences prefs; + + AnalyticsConsentStore(Context context) { + prefs = context.getApplicationContext() + .getSharedPreferences(BuildConfig.MULTICA_AUTH_PREFS_NAME + PREFS_SUFFIX, Context.MODE_PRIVATE); + } + + boolean hasDecision() { + return prefs.getBoolean(CONSENT_DECIDED, false); + } + + boolean isGranted() { + return prefs.getBoolean(CONSENT_DECIDED, false) + && prefs.getBoolean(CONSENT_GRANTED, false); + } + + void grant() { + prefs.edit() + .putBoolean(CONSENT_DECIDED, true) + .putBoolean(CONSENT_GRANTED, true) + .apply(); + } + + void deny() { + prefs.edit() + .putBoolean(CONSENT_DECIDED, true) + .putBoolean(CONSENT_GRANTED, false) + .apply(); + } +} diff --git a/app/src/main/java/ai/multica/app/AppAnalytics.java b/app/src/main/java/ai/multica/app/AppAnalytics.java new file mode 100644 index 0000000..20b07d5 --- /dev/null +++ b/app/src/main/java/ai/multica/app/AppAnalytics.java @@ -0,0 +1,277 @@ +package ai.multica.app; + +import android.content.Context; +import android.os.Build; +import android.text.TextUtils; +import android.util.Log; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; + +public final class AppAnalytics { + private static final String TAG = "MultiCasualAnalytics"; + static final String EVENT_APP_OPEN = "app_open"; + private static volatile boolean initialized; + private static volatile boolean appOpenTracked; + private static AnalyticsProvider provider = AnalyticsProvider.NO_OP; + + private AppAnalytics() {} + + public static boolean shouldPromptForConsent(Context context) { + return !new AnalyticsConsentStore(context).hasDecision(); + } + + public static void grantConsentAndInitialize(Context context) { + new AnalyticsConsentStore(context).grant(); + initializeIfAllowed(context); + } + + public static void denyConsent(Context context) { + new AnalyticsConsentStore(context).deny(); + provider.optOut(); + } + + public static void initializeIfAllowed(Context context) { + Context appContext = context.getApplicationContext(); + if (!new AnalyticsConsentStore(appContext).isGranted()) { + Log.i(TAG, "Analytics disabled until user grants consent."); + return; + } + if (initialized) { + trackAppOpenOnce(appContext); + return; + } + synchronized (AppAnalytics.class) { + if (initialized) { + trackAppOpenOnce(appContext); + return; + } + provider = createProvider(appContext); + initialized = true; + } + trackAppOpenOnce(appContext); + } + + static String selectedRouteForTesting(Context context) { + return selectRoute(context); + } + + private static AnalyticsProvider createProvider(Context context) { + String route = selectRoute(context); + Log.i(TAG, "Analytics route=" + route + " environment=" + BuildConfig.MULTICA_ENVIRONMENT); + if ("cn".equals(route)) { + return UmengAnalyticsProvider.create(context); + } + if ("global".equals(route)) { + return PostHogAnalyticsProvider.create(context); + } + return AnalyticsProvider.NO_OP; + } + + private static String selectRoute(Context context) { + String forced = safe(BuildConfig.ANALYTICS_ROUTE).toLowerCase(Locale.US); + if ("cn".equals(forced) || "global".equals(forced) || "none".equals(forced)) { + return forced; + } + if (isLikelyMainlandChina(context)) { + return "cn"; + } + return "global"; + } + + private static boolean isLikelyMainlandChina(Context context) { + Locale locale = Build.VERSION.SDK_INT >= 24 + ? context.getResources().getConfiguration().getLocales().get(0) + : context.getResources().getConfiguration().locale; + String country = locale == null ? "" : safe(locale.getCountry()).toUpperCase(Locale.US); + if ("CN".equals(country)) { + return true; + } + String language = locale == null ? "" : safe(locale.getLanguage()).toLowerCase(Locale.US); + String timeZone = TimeZone.getDefault().getID(); + return "zh".equals(language) + && ("Asia/Shanghai".equals(timeZone) + || "Asia/Chongqing".equals(timeZone) + || "Asia/Harbin".equals(timeZone) + || "Asia/Urumqi".equals(timeZone)); + } + + private static void trackAppOpenOnce(Context context) { + if (appOpenTracked) { + return; + } + synchronized (AppAnalytics.class) { + if (appOpenTracked) { + return; + } + appOpenTracked = true; + } + provider.trackAppOpen(appOpenProperties(context)); + } + + private static Map appOpenProperties(Context context) { + Map params = new LinkedHashMap<>(); + params.put("app_version", BuildConfig.VERSION_NAME); + params.put("build_flavor", BuildConfig.MULTICA_ENVIRONMENT); + params.put("distribution_channel", "public"); + params.put("analytics_route", selectRoute(context)); + return params; + } + + private static String safe(String value) { + return value == null ? "" : value.trim(); + } + + interface AnalyticsProvider { + AnalyticsProvider NO_OP = new AnalyticsProvider() { + @Override + public void trackAppOpen(Map properties) {} + + @Override + public void optOut() {} + }; + + void trackAppOpen(Map properties); + + void optOut(); + } + + private static final class UmengAnalyticsProvider implements AnalyticsProvider { + private final Context context; + private final Method onEventObjectMethod; + + private UmengAnalyticsProvider(Context context, Method onEventObjectMethod) { + this.context = context.getApplicationContext(); + this.onEventObjectMethod = onEventObjectMethod; + } + + static AnalyticsProvider create(Context context) { + if (!BuildConfig.UMENG_ENABLED || TextUtils.isEmpty(safe(BuildConfig.UMENG_APP_KEY))) { + Log.w(TAG, "Umeng selected but UMENG_APP_KEY is empty; analytics not started."); + return NO_OP; + } + Context appContext = context.getApplicationContext(); + try { + Class configureClass = Class.forName("com.umeng.commonsdk.UMConfigure"); + Class mobclickClass = Class.forName("com.umeng.analytics.MobclickAgent"); + Class pageModeClass = Class.forName("com.umeng.analytics.MobclickAgent$PageMode"); + Object manualMode = Enum.valueOf((Class) pageModeClass.asSubclass(Enum.class), "MANUAL"); + mobclickClass.getMethod("setPageCollectionMode", pageModeClass).invoke(null, manualMode); + mobclickClass.getMethod("setCatchUncaughtExceptions", boolean.class).invoke(null, false); + configureClass.getMethod("setLogEnabled", boolean.class).invoke(null, BuildConfig.DEBUG); + configureClass.getMethod("setProcessEvent", boolean.class).invoke(null, true); + configureClass.getMethod("init", Context.class, String.class, String.class, int.class, String.class) + .invoke(null, appContext, BuildConfig.UMENG_APP_KEY, BuildConfig.UMENG_CHANNEL, + configureClass.getField("DEVICE_TYPE_PHONE").getInt(null), null); + Method onEventObject = mobclickClass.getMethod("onEventObject", Context.class, String.class, Map.class); + Log.i(TAG, "Umeng analytics initialized."); + return new UmengAnalyticsProvider(appContext, onEventObject); + } catch (Throwable error) { + Log.w(TAG, "Umeng initialization failed; app launch continues.", error); + return NO_OP; + } + } + + @Override + public void trackAppOpen(Map properties) { + try { + onEventObjectMethod.invoke(null, context, EVENT_APP_OPEN, properties); + Log.i(TAG, "Umeng app_open tracked."); + } catch (Throwable error) { + Log.w(TAG, "Umeng app_open failed; app continues.", error); + } + } + + @Override + public void optOut() {} + } + + private static final class PostHogAnalyticsProvider implements AnalyticsProvider { + private final Object postHog; + private final Method captureMethod; + private final Method optOutMethod; + + private PostHogAnalyticsProvider(Object postHog, Method captureMethod, Method optOutMethod) { + this.postHog = postHog; + this.captureMethod = captureMethod; + this.optOutMethod = optOutMethod; + } + + static AnalyticsProvider create(Context context) { + if (!BuildConfig.POSTHOG_ENABLED || TextUtils.isEmpty(safe(BuildConfig.POSTHOG_API_KEY))) { + Log.w(TAG, "PostHog selected but POSTHOG_API_KEY is empty; analytics not started."); + return NO_OP; + } + try { + Class configClass = Class.forName("com.posthog.android.PostHogAndroidConfig"); + Object config = configClass.getConstructor(String.class, String.class) + .newInstance(BuildConfig.POSTHOG_API_KEY, BuildConfig.POSTHOG_HOST); + invokeSetter(configClass, config, "setCaptureApplicationLifecycleEvents", false); + invokeSetter(configClass, config, "setCaptureScreenViews", false); + invokeSetter(configClass, config, "setCaptureDeepLinks", false); + invokeSetter(configClass, config, "setSessionReplay", false); + invokeSetter(configClass, config, "setPreloadFeatureFlags", false); + invokeSetter(configClass, config, "setRemoteConfig", false); + invokeSetter(configClass, config, "setSurveys", false); + invokeSetter(configClass, config, "setSetDefaultPersonProperties", false); + invokeSetter(configClass, config, "setSendFeatureFlagEvent", false); + invokeSetter(configClass, config, "setDebug", BuildConfig.DEBUG); + + Class postHogAndroidClass = Class.forName("com.posthog.android.PostHogAndroid"); + Field companionField = postHogAndroidClass.getField("Companion"); + Object companion = companionField.get(null); + Object shared = companion.getClass() + .getMethod("with", Context.class, configClass) + .invoke(companion, context.getApplicationContext(), config); + Class interfaceClass = Class.forName("com.posthog.PostHogInterface"); + Method capture = interfaceClass.getMethod( + "capture", + String.class, + String.class, + Map.class, + Map.class, + Map.class, + Map.class, + Date.class + ); + Method optOut = interfaceClass.getMethod("optOut"); + Log.i(TAG, "PostHog analytics initialized."); + return new PostHogAnalyticsProvider(shared, capture, optOut); + } catch (Throwable error) { + Log.w(TAG, "PostHog initialization failed; app launch continues.", error); + return NO_OP; + } + } + + @Override + public void trackAppOpen(Map properties) { + try { + captureMethod.invoke(postHog, EVENT_APP_OPEN, null, properties, null, null, null, null); + Log.i(TAG, "PostHog app_open tracked."); + } catch (Throwable error) { + Log.w(TAG, "PostHog app_open failed; app continues.", error); + } + } + + @Override + public void optOut() { + try { + optOutMethod.invoke(postHog); + } catch (Throwable error) { + Log.w(TAG, "PostHog optOut failed; app continues.", error); + } + } + + private static void invokeSetter(Class configClass, Object config, String methodName, boolean value) { + try { + configClass.getMethod(methodName, boolean.class).invoke(config, value); + } catch (Throwable ignored) { + } + } + } +} diff --git a/app/src/main/java/ai/multica/app/ComposePilotActivity.kt b/app/src/main/java/ai/multica/app/ComposePilotActivity.kt index 6a011a7..f04238f 100644 --- a/app/src/main/java/ai/multica/app/ComposePilotActivity.kt +++ b/app/src/main/java/ai/multica/app/ComposePilotActivity.kt @@ -548,8 +548,11 @@ private fun ComposePilotApp( initialIssueId: String? = null, initialTabName: String? = null, ) { + val context = LocalContext.current var appState by remember { mutableStateOf(PilotState.Loading) } var refresh by remember { mutableIntStateOf(0) } + var showAnalyticsConsent by remember { mutableStateOf(AppAnalytics.shouldPromptForConsent(context)) } + val zh = authStore.isChinese() LaunchedEffect(refresh) { appState = PilotState.Loading @@ -612,6 +615,27 @@ private fun ComposePilotApp( onSessionUpdated = { appState = PilotState.Ready(it) }, ) } + + MulticaCupertinoAlertDialog( + visible = showAnalyticsConsent, + title = if (zh) "允许基础使用统计?" else "Allow basic usage analytics?", + message = if (zh) { + "Multi-Casual 只会上报 app_open 启动事件,用于统计用户量和日活。不会采集页面浏览、聊天内容、文件内容、邮箱、Token 或精确位置。中国大陆用户走友盟+,海外用户走 PostHog。你可以选择不同意,应用仍可继续使用。" + } else { + "Multi-Casual only reports the app_open event to measure user count and daily active users. It does not collect page views, chat content, file content, email, tokens, or precise location. Mainland China routes to Umeng+, while global users route to PostHog. You can decline and still use the app." + }, + cancelText = if (zh) "不同意" else "Decline", + confirmText = if (zh) "同意并继续" else "Agree", + contentDescription = "Analytics Consent", + onDismissRequest = { + AppAnalytics.denyConsent(context) + showAnalyticsConsent = false + }, + onConfirm = { + AppAnalytics.grantConsentAndInitialize(context) + showAnalyticsConsent = false + }, + ) } @Composable @@ -710,7 +734,7 @@ private fun PilotLoginScreen( ) } Text( - text = if (zh) "登录 Multica" else "Sign in to Multica", + text = if (zh) "登录 Multi-Casual" else "Sign in to Multi-Casual", style = MaterialTheme.typography.titleLarge.copy(fontSize = 30.sp, lineHeight = 36.sp, fontWeight = FontWeight.Bold), color = MulticaColors.Text, textAlign = TextAlign.Center, @@ -983,7 +1007,7 @@ private fun PilotNoWorkspaceOnboarding( verticalArrangement = Arrangement.spacedBy(7.dp), ) { Text( - text = "Multica", + text = "Multi-Casual", style = MaterialTheme.typography.titleLarge.copy(fontSize = 28.sp, lineHeight = 33.sp), color = MulticaColors.Text, fontWeight = FontWeight.Bold, @@ -2023,7 +2047,7 @@ private fun PilotSearchPage( } PilotListPage( - title = if (zh) "搜索 Multica" else "Search Multica", + title = if (zh) "搜索 Multi-Casual" else "Search Multi-Casual", leading = { PilotSecondaryBackButton(zh = zh, onBack = onBack) }, ) { item { @@ -3846,7 +3870,7 @@ private fun ChatWindowWebEmptyState( verticalArrangement = Arrangement.spacedBy(12.dp), ) { MulticaEmptyState( - title = if (zh) "欢迎使用 Multica" else "Welcome to Multica", + title = if (zh) "欢迎使用 Multi-Casual" else "Welcome to Multi-Casual", description = if (zh) "试试问" else "Try asking", ) Column( @@ -13444,11 +13468,11 @@ private fun PilotInlineResultBannersPreview() { .put("id", "comment-preview") .put("content", "Inbox opened this issue at the exact comment.") .put("author_type", "member") - .put("author_name", "Multica") + .put("author_name", "Multi-Casual") .put("created_at", "2026-05-14T08:00:00Z") ) }, - authorName = "Multica", + authorName = "Multi-Casual", expandedActions = false, contentExpanded = true, highlighted = true, @@ -24654,7 +24678,7 @@ private fun feedbackTypeOptions(zh: Boolean): List = listOf( FeedbackTypeOption( key = "feature", label = if (zh) "功能建议" else "Feature request", - detail = if (zh) "一个希望 Multica 支持的新能力。" else "A capability you want Multica to support.", + detail = if (zh) "一个希望 Multi-Casual 支持的新能力。" else "A capability you want Multi-Casual to support.", icon = Icons.Outlined.Bolt, ), FeedbackTypeOption( @@ -26673,9 +26697,9 @@ private fun AgentsWebEmptyState( MulticaEmptyState( title = if (zh) "还没有智能体" else "No agents yet", description = if (zh) { - "创建一个智能体,像分配给同事那样把 issue 交给它。本地智能体在你的机器上运行,云智能体在 Multica 运行时上运行。" + "创建一个智能体,像分配给同事那样把 issue 交给它。本地智能体在你的机器上运行,云智能体在 Multi-Casual 运行时上运行。" } else { - "Create an agent and assign it issues, like any teammate. Local agents run on your machine; cloud agents run on Multica's runtime." + "Create an agent and assign it issues, like any teammate. Local agents run on your machine; cloud agents run on Multi-Casual's runtime." }, ) } diff --git a/app/src/main/java/ai/multica/app/MainActivity.java b/app/src/main/java/ai/multica/app/MainActivity.java index 1b7222e..1d05439 100644 --- a/app/src/main/java/ai/multica/app/MainActivity.java +++ b/app/src/main/java/ai/multica/app/MainActivity.java @@ -247,7 +247,7 @@ public final class MainActivity extends Activity { header.setGravity(Gravity.CENTER_VERTICAL); header.setPadding(dp(18), dp(12), dp(14), dp(10)); header.setBackgroundColor(0xFFFFFFFF); - TextView title = label("Multica", 22, TEXT, true); + TextView title = label("Multi-Casual", 22, TEXT, true); header.addView(title, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1)); if (!workspaces.isEmpty()) { @@ -2190,7 +2190,7 @@ public final class MainActivity extends Activity { switch (key) { case "loading": return "加载中..."; case "sessionExpired": return "登录已过期,请重新登录"; - case "signIn": return "登录 Multica"; + case "signIn": return "登录 Multi-Casual"; case "signInHint": return "输入邮箱获取登录验证码"; case "continue": return "继续"; case "google": return "使用 Google 继续"; @@ -2303,7 +2303,7 @@ public final class MainActivity extends Activity { switch (key) { case "loading": return "Loading..."; case "sessionExpired": return "Session expired. Please sign in again."; - case "signIn": return "Sign in to Multica"; + case "signIn": return "Sign in to Multi-Casual"; case "signInHint": return "Enter your email to get a login code"; case "continue": return "Continue"; case "google": return "Continue with Google"; diff --git a/app/src/main/java/ai/multica/app/MulticaApplication.java b/app/src/main/java/ai/multica/app/MulticaApplication.java index ccf59f8..2da46b2 100644 --- a/app/src/main/java/ai/multica/app/MulticaApplication.java +++ b/app/src/main/java/ai/multica/app/MulticaApplication.java @@ -6,5 +6,6 @@ public final class MulticaApplication extends Application { @Override public void onCreate() { super.onCreate(); + AppAnalytics.initializeIfAllowed(this); } } diff --git a/app/src/main/java/ai/multica/app/OnboardingPayload.java b/app/src/main/java/ai/multica/app/OnboardingPayload.java index d3d9603..623f48b 100644 --- a/app/src/main/java/ai/multica/app/OnboardingPayload.java +++ b/app/src/main/java/ai/multica/app/OnboardingPayload.java @@ -19,11 +19,11 @@ final class OnboardingPayload { JSONObject payload = new JSONObject(); payload.put("workspace_id", workspaceId); payload.put("project", new JSONObject() - .put("title", "Getting Started with Multica") - .put("description", "A lightweight starter project for learning Multica on mobile.") + .put("title", "Getting Started with Multi-Casual") + .put("description", "A lightweight starter project for learning Multi-Casual on mobile.") .put("icon", "sparkles")); payload.put("welcome_issue_template", new JSONObject() - .put("title", "Welcome to Multica") + .put("title", "Welcome to Multi-Casual") .put("description", "Use this issue to test comments, Markdown, status changes, and agent collaboration.") .put("priority", "high")); payload.put("agent_guided_sub_issues", new JSONArray() diff --git a/app/src/main/java/ai/multica/app/ui/components/MulticaComponents.kt b/app/src/main/java/ai/multica/app/ui/components/MulticaComponents.kt index 273296a..e08e53a 100644 --- a/app/src/main/java/ai/multica/app/ui/components/MulticaComponents.kt +++ b/app/src/main/java/ai/multica/app/ui/components/MulticaComponents.kt @@ -222,7 +222,7 @@ private fun MulticaGlobalActionsBar( ) } Text( - text = workspaceName.ifBlank { "Multica" }, + text = workspaceName.ifBlank { "Multi-Casual" }, style = MaterialTheme.typography.labelMedium.copy(fontSize = 12.sp, lineHeight = 15.sp, fontWeight = FontWeight.SemiBold), color = MulticaColors.Text, maxLines = 1, @@ -909,14 +909,14 @@ fun MulticaBottomNav( Modifier.hazeEffect( hazeState, style = HazeDefaults.style( - backgroundColor = MulticaColors.SurfaceElevated.copy(alpha = 0.72f), + backgroundColor = MulticaColors.SurfaceElevated, blurRadius = 28.dp, noiseFactor = 0.07f, ), ) } ) - .background(MulticaColors.SurfaceElevated.copy(alpha = 0.72f)) + .background(MulticaColors.SurfaceElevated) .border(0.5.dp, MulticaColors.Border.copy(alpha = 0.62f), RoundedCornerShape(28.dp)) .padding(horizontal = 6.dp, vertical = 6.dp), horizontalArrangement = Arrangement.spacedBy(3.dp), diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 95f4679..615d279 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,3 @@ - Multica + Multi-Casual