diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt index 332aff75e..1bff1dcc2 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt @@ -762,14 +762,14 @@ class ConfigurationFragment @JvmOverloads constructor( 2 -> { profileStatusText = profile.error - profileStatusColor = context.getColour(R.color.material_red_500) + profileStatusColor = context.getColorAttr(R.attr.testFailColor) } 3 -> { val err = profile.error ?: "" val msg = Protocols.genFriendlyMsg(err) profileStatusText = if (msg != err) msg else getString(R.string.unavailable) - profileStatusColor = context.getColour(R.color.material_red_500) + profileStatusColor = context.getColorAttr(R.attr.testFailColor) } } @@ -1803,6 +1803,7 @@ class ConfigurationFragment @JvmOverloads constructor( } profileName.text = proxyEntity.displayName() + profileName.setTextColor(requireContext().getColorAttr(R.attr.profileNameColor)) profileType.text = proxyEntity.displayType() profileType.setTextColor(requireContext().getProtocolColor(proxyEntity.type)) @@ -1854,7 +1855,7 @@ class ConfigurationFragment @JvmOverloads constructor( profileStatus.text = getString(R.string.available, proxyEntity.ping) profileStatus.setTextColor(requireContext().getColour(R.color.material_green_500)) } else { - profileStatus.setTextColor(requireContext().getColour(R.color.material_red_500)) + profileStatus.setTextColor(requireContext().getColorAttr(R.attr.testFailColor)) if (proxyEntity.status == 2) { profileStatus.text = proxyEntity.error } diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/RouteFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/RouteFragment.kt index cf4e7aa6c..25720e9f1 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/RouteFragment.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/RouteFragment.kt @@ -7,6 +7,7 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.widget.Toolbar import androidx.core.content.ContextCompat +import io.nekohasekai.sagernet.ktx.getColorAttr import androidx.core.view.ViewCompat import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView @@ -287,13 +288,14 @@ class RouteFragment : ToolbarFragment(R.layout.layout_route), Toolbar.OnMenuItem routeOutbound.text = rule.displayOutbound() // set text color based on route type - val colorRes = when (rule.outbound) { - -2L -> R.color.color_route_block // block: red - -1L -> R.color.color_route_direct // direct: green - 0L -> R.color.color_route_proxy // proxy: blue - else -> R.color.color_route_config // config: purple + val ctx = itemView.context + val outboundColor = when (rule.outbound) { + -2L -> ContextCompat.getColor(ctx, R.color.color_route_block) // block: red + -1L -> ContextCompat.getColor(ctx, R.color.color_route_direct) // direct: green + 0L -> ctx.getColorAttr(R.attr.routeProxyColor) // proxy: blue/cyan + else -> ContextCompat.getColor(ctx, R.color.color_route_config) // config: purple } - routeOutbound.setTextColor(ContextCompat.getColor(itemView.context, colorRes)) + routeOutbound.setTextColor(outboundColor) itemView.setOnClickListener { enableSwitch.performClick() diff --git a/app/src/main/java/io/nekohasekai/sagernet/widget/ServiceButton.kt b/app/src/main/java/io/nekohasekai/sagernet/widget/ServiceButton.kt index b86659e60..e8e0183f4 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/widget/ServiceButton.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/widget/ServiceButton.kt @@ -1,6 +1,7 @@ package io.nekohasekai.sagernet.widget import android.content.Context +import android.content.res.ColorStateList import android.graphics.drawable.Drawable import android.os.Build import android.util.AttributeSet @@ -17,6 +18,7 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.progressindicator.BaseProgressIndicator import io.nekohasekai.sagernet.R import io.nekohasekai.sagernet.bg.BaseService +import io.nekohasekai.sagernet.ktx.getColorAttr import kotlinx.coroutines.Job import kotlinx.coroutines.delay import java.util.* @@ -118,6 +120,7 @@ class ServiceButton @JvmOverloads constructor( } checked = state == BaseService.State.Connected refreshDrawableState() + applyStateTint(state) val description = context.getText(if (state.canStop) R.string.stop else R.string.connect) contentDescription = description TooltipCompat.setTooltipText(this, description) @@ -129,6 +132,18 @@ class ServiceButton @JvmOverloads constructor( ) } + private fun applyStateTint(state: BaseService.State) { + // Tint the connect FAB icon by state: connected=green, stopped=red. For + // transient states (and on non-Dracula themes) fall back to colorOnPrimary, + // which is the FAB's normal icon color, so those themes look unchanged. + val attr = when (state) { + BaseService.State.Connected -> R.attr.statusConnectedColor + BaseService.State.Stopped -> R.attr.fabStoppedColor + else -> com.google.android.material.R.attr.colorOnPrimary + } + imageTintList = ColorStateList.valueOf(context.getColorAttr(attr)) + } + private fun changeState(icon: AnimatedState, animate: Boolean) { fun counters(a: AnimatedState, b: AnimatedState): Boolean = a == iconStopped && b == iconConnecting || diff --git a/app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt b/app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt index 2116c237e..821670db2 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt @@ -2,7 +2,9 @@ package io.nekohasekai.sagernet.widget import android.annotation.SuppressLint import android.content.Context +import android.text.SpannableStringBuilder import android.text.format.Formatter +import android.text.style.ForegroundColorSpan import android.util.AttributeSet import android.view.View import android.widget.TextView @@ -80,6 +82,43 @@ class StatsBar @JvmOverloads constructor( TooltipCompat.setTooltipText(this, text) } + // Two-tone status: color the lead segment (split at [sep], kept with the lead) + // and the remainder separately. Used for "Connected, …" and "Success: …". + private fun setStatusTwoTone( + full: CharSequence, sep: Char, leadAttr: Int, restAttr: Int + ) { + val s = full.toString() + val idx = s.indexOf(sep) + if (idx < 0) { + statusText.setTextColor(context.getColorAttr(leadAttr)) + setStatus(full) + return + } + val cut = idx + 1 // keep the separator char with the lead segment + val span = SpannableStringBuilder(s) + span.setSpan( + ForegroundColorSpan(context.getColorAttr(leadAttr)), + 0, cut, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE + ) + span.setSpan( + ForegroundColorSpan(context.getColorAttr(restAttr)), + cut, s.length, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE + ) + setStatus(span) + } + + private fun setStatusColorByState(state: BaseService.State) { + // Connected = statusConnectedColor (green), Stopped/Stopping = statusStoppedColor + // (red — "Shutting down…" reads as red), Connecting/other = colorOnPrimary. + // Non-Dracula themes default these attrs to colorOnPrimary, so no change. + val attr = when (state) { + BaseService.State.Connected -> R.attr.statusConnectedColor + BaseService.State.Stopped, BaseService.State.Stopping -> R.attr.statusStoppedColor + else -> com.google.android.material.R.attr.colorOnPrimary + } + statusText.setTextColor(context.getColorAttr(attr)) + } + fun changeState(state: BaseService.State) { val activity = context as MainActivity fun postWhenStarted(what: () -> Unit) = activity.lifecycleScope.launch(Dispatchers.Main) { @@ -89,13 +128,18 @@ class StatsBar @JvmOverloads constructor( if ((state == BaseService.State.Connected).also { hideOnScroll = it }) { postWhenStarted { if (allowShow) performShow() - setStatus(app.getText(R.string.vpn_connected)) + // "Connected," in green; the "tap to check connection" hint in detail color. + setStatusTwoTone( + app.getText(R.string.vpn_connected), ',', + R.attr.statusConnectedColor, R.attr.statusDetailColor + ) } } else { postWhenStarted { performHide() } updateSpeed(0, 0) + setStatusColorByState(state) setStatus( context.getText( when (state) { @@ -110,6 +154,9 @@ class StatsBar @JvmOverloads constructor( @SuppressLint("SetTextI18n") fun updateSpeed(txRate: Long, rxRate: Long) { + val speedColor = context.getColorAttr(R.attr.speedTextColor) + txText.setTextColor(speedColor) + rxText.setTextColor(speedColor) txText.text = "▲ ${ context.getString( R.string.speed, Formatter.formatFileSize(context, txRate) @@ -125,20 +172,24 @@ class StatsBar @JvmOverloads constructor( fun testConnection() { val activity = context as MainActivity isEnabled = false + // "Testing…" in the testing color. + statusText.setTextColor(context.getColorAttr(R.attr.statusTestingColor)) setStatus(app.getText(R.string.connection_test_testing)) runOnDefaultDispatcher { try { val elapsed = activity.urlTest() onMainDispatcher { isEnabled = true - setStatus( + // "Success:" in green; the handshake detail in detail color. + setStatusTwoTone( app.getString( if (DataStore.connectionTestURL.startsWith("https://")) { R.string.connection_test_available } else { R.string.connection_test_available_http }, elapsed - ) + ), ':', + R.attr.statusConnectedColor, R.attr.statusDetailColor ) } @@ -146,6 +197,7 @@ class StatsBar @JvmOverloads constructor( Logs.w(e.toString()) onMainDispatcher { isEnabled = true + statusText.setTextColor(context.getColorAttr(R.attr.statusTestingColor)) setStatus(app.getText(R.string.connection_test_testing)) activity.snackbar( diff --git a/app/src/main/java/moe/matsuri/nb4a/Protocols.kt b/app/src/main/java/moe/matsuri/nb4a/Protocols.kt index 28f826800..12d1dbdee 100644 --- a/app/src/main/java/moe/matsuri/nb4a/Protocols.kt +++ b/app/src/main/java/moe/matsuri/nb4a/Protocols.kt @@ -44,7 +44,7 @@ object Protocols { fun Context.getProtocolColor(type: Int): Int { return when (type) { TYPE_NEKO -> getColorAttr(android.R.attr.textColorPrimary) - else -> getColorAttr(R.attr.accentOrTextSecondary) + else -> getColorAttr(R.attr.protocolColor) } } diff --git a/app/src/main/res/color/nav_item_fill.xml b/app/src/main/res/color/nav_item_fill.xml index f23d44ec7..d3cdd7475 100644 --- a/app/src/main/res/color/nav_item_fill.xml +++ b/app/src/main/res/color/nav_item_fill.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/color/navigation_icon.xml b/app/src/main/res/color/navigation_icon.xml index 99c3f94f7..eff42755a 100644 --- a/app/src/main/res/color/navigation_icon.xml +++ b/app/src/main/res/color/navigation_icon.xml @@ -1,5 +1,5 @@ - + - \ No newline at end of file + diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index 9b3c36627..ff5dc33b4 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -26,6 +26,19 @@ @color/dracula_background @color/dracula_on_surface @color/dracula_background + + @color/color_dracula_green + @color/color_dracula_yellow + @color/color_dracula_green + @color/color_dracula_red + @color/color_dracula_red + @color/color_dracula_cyan + @color/color_dracula_cyan + @color/color_dracula_cyan + @color/color_dracula_yellow + @color/color_dracula_cyan + ?attr/colorAccent diff --git a/app/src/main/res/values-v26/themes.xml b/app/src/main/res/values-v26/themes.xml index 6bce0da1c..6b1efb0c9 100644 --- a/app/src/main/res/values-v26/themes.xml +++ b/app/src/main/res/values-v26/themes.xml @@ -5,6 +5,18 @@ ?colorPrimary + + ?attr/accentOrTextSecondary + ?android:attr/textColorPrimary + ?attr/colorOnPrimary + ?attr/colorOnPrimary + @color/material_red_500 + ?attr/colorOnPrimary + ?attr/colorOnPrimary + ?attr/colorOnPrimary + ?attr/colorOnPrimary + @color/color_route_proxy + ?attr/colorPrimary @drawable/ic_navigation_close ?colorAccent @android:color/transparent diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 20cef29eb..bda8ff5b5 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -10,6 +10,33 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 1fc3610ac..cad484329 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -300,6 +300,14 @@ #FF79C6 #E9DDFB #9A7BD0 + + #50FA7B + + #FF5555 + + #F1FA8C + + #8BE9FD #9E9E9E #2B2B2B diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index e06ebb322..021a3934a 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -2,6 +2,20 @@