Release QMAX 1.0.0 with customizable themes
This commit is contained in:
@@ -22,8 +22,8 @@ android {
|
|||||||
applicationId = "xyz.kusoft.qmax"
|
applicationId = "xyz.kusoft.qmax"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 55
|
versionCode = 57
|
||||||
versionName = "0.1.54"
|
versionName = "1.0.0"
|
||||||
|
|
||||||
buildConfigField("String", "QMAX_DEFAULT_SERVER_URL", "\"https://qmax.kusoft.xyz\"")
|
buildConfigField("String", "QMAX_DEFAULT_SERVER_URL", "\"https://qmax.kusoft.xyz\"")
|
||||||
buildConfigField("String", "QMAX_DEFAULT_PAIRING_CODE", "\"qmax-MxRq4h2HQBEIFs6k\"")
|
buildConfigField("String", "QMAX_DEFAULT_PAIRING_CODE", "\"qmax-MxRq4h2HQBEIFs6k\"")
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,10 +5,12 @@ import xyz.kusoft.qmax.core.local.AvatarDiskCache
|
|||||||
import xyz.kusoft.qmax.core.local.AttachmentDiskCache
|
import xyz.kusoft.qmax.core.local.AttachmentDiskCache
|
||||||
import xyz.kusoft.qmax.core.local.LocalMessageCache
|
import xyz.kusoft.qmax.core.local.LocalMessageCache
|
||||||
import xyz.kusoft.qmax.core.network.QMaxApi
|
import xyz.kusoft.qmax.core.network.QMaxApi
|
||||||
|
import xyz.kusoft.qmax.core.settings.AppearanceStore
|
||||||
import xyz.kusoft.qmax.core.settings.MessageDraftStore
|
import xyz.kusoft.qmax.core.settings.MessageDraftStore
|
||||||
import xyz.kusoft.qmax.core.settings.TokenStore
|
import xyz.kusoft.qmax.core.settings.TokenStore
|
||||||
|
|
||||||
class QMaxContainer(context: Context) {
|
class QMaxContainer(context: Context) {
|
||||||
|
val appearanceStore = AppearanceStore(context)
|
||||||
val tokenStore = TokenStore(context)
|
val tokenStore = TokenStore(context)
|
||||||
val draftStore = MessageDraftStore(context)
|
val draftStore = MessageDraftStore(context)
|
||||||
val messageCache = LocalMessageCache(context)
|
val messageCache = LocalMessageCache(context)
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package xyz.kusoft.qmax.core.settings
|
||||||
|
|
||||||
|
enum class ThemeMode {
|
||||||
|
System,
|
||||||
|
Light,
|
||||||
|
Dark
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ChatPalette(
|
||||||
|
val accent: Int,
|
||||||
|
val chatBackground: Int,
|
||||||
|
val incomingBubble: Int,
|
||||||
|
val outgoingBubble: Int,
|
||||||
|
val messageText: Int,
|
||||||
|
val patternColor: Int,
|
||||||
|
val showPattern: Boolean = true
|
||||||
|
)
|
||||||
|
|
||||||
|
object ChatPalettes {
|
||||||
|
val Light = ChatPalette(
|
||||||
|
accent = 0xFF3390EC.toInt(),
|
||||||
|
chatBackground = 0xFFE7EEF3.toInt(),
|
||||||
|
incomingBubble = 0xFFFFFFFF.toInt(),
|
||||||
|
outgoingBubble = 0xFFE7F7C8.toInt(),
|
||||||
|
messageText = 0xFF17212B.toInt(),
|
||||||
|
patternColor = 0xFF6C7883.toInt()
|
||||||
|
)
|
||||||
|
|
||||||
|
val Dark = ChatPalette(
|
||||||
|
accent = 0xFF5AA7E8.toInt(),
|
||||||
|
chatBackground = 0xFF0E1621.toInt(),
|
||||||
|
incomingBubble = 0xFF182533.toInt(),
|
||||||
|
outgoingBubble = 0xFF2B5278.toInt(),
|
||||||
|
messageText = 0xFFF5F7FA.toInt(),
|
||||||
|
patternColor = 0xFF6E7F91.toInt()
|
||||||
|
)
|
||||||
|
|
||||||
|
val OceanLight = Light.copy(
|
||||||
|
accent = 0xFF168ACD.toInt(),
|
||||||
|
chatBackground = 0xFFDCEEF6.toInt(),
|
||||||
|
incomingBubble = 0xFFF8FCFE.toInt(),
|
||||||
|
outgoingBubble = 0xFFCDEAF7.toInt(),
|
||||||
|
patternColor = 0xFF5192AE.toInt()
|
||||||
|
)
|
||||||
|
|
||||||
|
val MintLight = Light.copy(
|
||||||
|
accent = 0xFF20A98A.toInt(),
|
||||||
|
chatBackground = 0xFFDDEFE8.toInt(),
|
||||||
|
incomingBubble = 0xFFFFFFFF.toInt(),
|
||||||
|
outgoingBubble = 0xFFCFEFDF.toInt(),
|
||||||
|
patternColor = 0xFF4C8C78.toInt()
|
||||||
|
)
|
||||||
|
|
||||||
|
val MidnightDark = Dark.copy(
|
||||||
|
accent = 0xFF8774E1.toInt(),
|
||||||
|
chatBackground = 0xFF10131A.toInt(),
|
||||||
|
incomingBubble = 0xFF20232D.toInt(),
|
||||||
|
outgoingBubble = 0xFF5B4C95.toInt(),
|
||||||
|
patternColor = 0xFF666B85.toInt()
|
||||||
|
)
|
||||||
|
|
||||||
|
val ForestDark = Dark.copy(
|
||||||
|
accent = 0xFF4DBB8B.toInt(),
|
||||||
|
chatBackground = 0xFF101A17.toInt(),
|
||||||
|
incomingBubble = 0xFF1B2924.toInt(),
|
||||||
|
outgoingBubble = 0xFF285C4A.toInt(),
|
||||||
|
patternColor = 0xFF55796D.toInt()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class AppearanceSettings(
|
||||||
|
val mode: ThemeMode = ThemeMode.System,
|
||||||
|
val lightPalette: ChatPalette = ChatPalettes.Light,
|
||||||
|
val darkPalette: ChatPalette = ChatPalettes.Dark,
|
||||||
|
val chatFontSizeSp: Float = 16f,
|
||||||
|
val chatLineSpacingSp: Float = 6f
|
||||||
|
) {
|
||||||
|
fun palette(isDark: Boolean): ChatPalette = if (isDark) darkPalette else lightPalette
|
||||||
|
|
||||||
|
fun withPalette(isDark: Boolean, palette: ChatPalette): AppearanceSettings {
|
||||||
|
return if (isDark) copy(darkPalette = palette) else copy(lightPalette = palette)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetPalette(isDark: Boolean): AppearanceSettings {
|
||||||
|
return withPalette(isDark, if (isDark) ChatPalettes.Dark else ChatPalettes.Light)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package xyz.kusoft.qmax.core.settings
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||||
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import androidx.datastore.preferences.core.floatPreferencesKey
|
||||||
|
import androidx.datastore.preferences.core.intPreferencesKey
|
||||||
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
private val Context.qmaxAppearanceDataStore by preferencesDataStore("qmax_appearance")
|
||||||
|
|
||||||
|
class AppearanceStore(private val context: Context) {
|
||||||
|
private val modeKey = stringPreferencesKey("theme_mode")
|
||||||
|
private val chatFontSizeKey = floatPreferencesKey("chat_font_size_sp")
|
||||||
|
private val chatLineSpacingKey = floatPreferencesKey("chat_line_spacing_sp")
|
||||||
|
|
||||||
|
private val lightKeys = PaletteKeys("light")
|
||||||
|
private val darkKeys = PaletteKeys("dark")
|
||||||
|
|
||||||
|
val settings: Flow<AppearanceSettings> = context.qmaxAppearanceDataStore.data.map { preferences ->
|
||||||
|
val mode = preferences[modeKey]
|
||||||
|
?.let { saved -> ThemeMode.entries.firstOrNull { it.name == saved } }
|
||||||
|
?: ThemeMode.System
|
||||||
|
AppearanceSettings(
|
||||||
|
mode = mode,
|
||||||
|
lightPalette = preferences.readPalette(lightKeys, ChatPalettes.Light),
|
||||||
|
darkPalette = preferences.readPalette(darkKeys, ChatPalettes.Dark),
|
||||||
|
chatFontSizeSp = (preferences[chatFontSizeKey] ?: 16f).coerceIn(12f, 24f),
|
||||||
|
chatLineSpacingSp = (preferences[chatLineSpacingKey] ?: 6f).coerceIn(0f, 12f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun save(settings: AppearanceSettings) {
|
||||||
|
context.qmaxAppearanceDataStore.edit { preferences ->
|
||||||
|
preferences[modeKey] = settings.mode.name
|
||||||
|
preferences.writePalette(lightKeys, settings.lightPalette)
|
||||||
|
preferences.writePalette(darkKeys, settings.darkPalette)
|
||||||
|
preferences[chatFontSizeKey] = settings.chatFontSizeSp.coerceIn(12f, 24f)
|
||||||
|
preferences[chatLineSpacingKey] = settings.chatLineSpacingSp.coerceIn(0f, 12f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class PaletteKeys(val prefix: String) {
|
||||||
|
val accent = intPreferencesKey("${prefix}_accent")
|
||||||
|
val chatBackground = intPreferencesKey("${prefix}_chat_background")
|
||||||
|
val incomingBubble = intPreferencesKey("${prefix}_incoming_bubble")
|
||||||
|
val outgoingBubble = intPreferencesKey("${prefix}_outgoing_bubble")
|
||||||
|
val messageText = intPreferencesKey("${prefix}_message_text")
|
||||||
|
val patternColor = intPreferencesKey("${prefix}_pattern_color")
|
||||||
|
val showPattern = booleanPreferencesKey("${prefix}_show_pattern")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Preferences.readPalette(keys: PaletteKeys, fallback: ChatPalette): ChatPalette {
|
||||||
|
return ChatPalette(
|
||||||
|
accent = this[keys.accent] ?: fallback.accent,
|
||||||
|
chatBackground = this[keys.chatBackground] ?: fallback.chatBackground,
|
||||||
|
incomingBubble = this[keys.incomingBubble] ?: fallback.incomingBubble,
|
||||||
|
outgoingBubble = this[keys.outgoingBubble] ?: fallback.outgoingBubble,
|
||||||
|
messageText = this[keys.messageText] ?: fallback.messageText,
|
||||||
|
patternColor = this[keys.patternColor] ?: fallback.patternColor,
|
||||||
|
showPattern = this[keys.showPattern] ?: fallback.showPattern
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun androidx.datastore.preferences.core.MutablePreferences.writePalette(
|
||||||
|
keys: PaletteKeys,
|
||||||
|
palette: ChatPalette
|
||||||
|
) {
|
||||||
|
this[keys.accent] = palette.accent
|
||||||
|
this[keys.chatBackground] = palette.chatBackground
|
||||||
|
this[keys.incomingBubble] = palette.incomingBubble
|
||||||
|
this[keys.outgoingBubble] = palette.outgoingBubble
|
||||||
|
this[keys.messageText] = palette.messageText
|
||||||
|
this[keys.patternColor] = palette.patternColor
|
||||||
|
this[keys.showPattern] = palette.showPattern
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,35 +1,221 @@
|
|||||||
package xyz.kusoft.qmax.ui.theme
|
package xyz.kusoft.qmax.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.material3.ColorScheme
|
import androidx.compose.material3.ColorScheme
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.darkColorScheme
|
||||||
import androidx.compose.material3.lightColorScheme
|
import androidx.compose.material3.lightColorScheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.luminance
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import xyz.kusoft.qmax.core.settings.AppearanceSettings
|
||||||
|
import xyz.kusoft.qmax.core.settings.ChatPalette
|
||||||
|
import xyz.kusoft.qmax.core.settings.ThemeMode
|
||||||
|
|
||||||
val QMaxBlue = Color(0xFF3390EC)
|
private data class QMaxThemeColors(
|
||||||
val QMaxText = Color(0xFF17212B)
|
val accent: Color,
|
||||||
val QMaxMuted = Color(0xFF6C7883)
|
val onAccent: Color,
|
||||||
val QMaxBackground = Color(0xFFF3F7FA)
|
val appBackground: Color,
|
||||||
val QMaxIncoming = Color.White
|
val surface: Color,
|
||||||
val QMaxOutgoing = Color(0xFFE7F7C8)
|
val surfaceVariant: Color,
|
||||||
val QMaxUnread = QMaxBlue
|
val divider: Color,
|
||||||
|
val text: Color,
|
||||||
private val Colors: ColorScheme = lightColorScheme(
|
val muted: Color,
|
||||||
primary = QMaxBlue,
|
val chatBackground: Color,
|
||||||
onPrimary = Color.White,
|
val incomingBubble: Color,
|
||||||
background = QMaxBackground,
|
val outgoingBubble: Color,
|
||||||
onBackground = QMaxText,
|
val messageText: Color,
|
||||||
surface = Color.White,
|
val pattern: Color,
|
||||||
onSurface = QMaxText,
|
val showPattern: Boolean,
|
||||||
surfaceVariant = Color(0xFFE9EEF2),
|
val isDark: Boolean,
|
||||||
onSurfaceVariant = QMaxMuted
|
val chatFontSizeSp: Float,
|
||||||
|
val chatLineSpacingSp: Float
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val DefaultAppearance = AppearanceSettings()
|
||||||
|
private val DefaultColors = colorsFor(
|
||||||
|
palette = DefaultAppearance.lightPalette,
|
||||||
|
isDark = false,
|
||||||
|
chatFontSizeSp = DefaultAppearance.chatFontSizeSp,
|
||||||
|
chatLineSpacingSp = DefaultAppearance.chatLineSpacingSp
|
||||||
|
)
|
||||||
|
private val LocalQMaxColors = staticCompositionLocalOf { DefaultColors }
|
||||||
|
|
||||||
|
val QMaxBlue: Color
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() = LocalQMaxColors.current.accent
|
||||||
|
|
||||||
|
val QMaxOnAccent: Color
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() = LocalQMaxColors.current.onAccent
|
||||||
|
|
||||||
|
val QMaxText: Color
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() = LocalQMaxColors.current.text
|
||||||
|
|
||||||
|
val QMaxMessageText: Color
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() = LocalQMaxColors.current.messageText
|
||||||
|
|
||||||
|
val QMaxMuted: Color
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() = LocalQMaxColors.current.muted
|
||||||
|
|
||||||
|
val QMaxBackground: Color
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() = LocalQMaxColors.current.appBackground
|
||||||
|
|
||||||
|
val QMaxSurface: Color
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() = LocalQMaxColors.current.surface
|
||||||
|
|
||||||
|
val QMaxSurfaceVariant: Color
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() = LocalQMaxColors.current.surfaceVariant
|
||||||
|
|
||||||
|
val QMaxDivider: Color
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() = LocalQMaxColors.current.divider
|
||||||
|
|
||||||
|
val QMaxChatBackground: Color
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() = LocalQMaxColors.current.chatBackground
|
||||||
|
|
||||||
|
val QMaxIncoming: Color
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() = LocalQMaxColors.current.incomingBubble
|
||||||
|
|
||||||
|
val QMaxOutgoing: Color
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() = LocalQMaxColors.current.outgoingBubble
|
||||||
|
|
||||||
|
val QMaxPattern: Color
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() = LocalQMaxColors.current.pattern
|
||||||
|
|
||||||
|
val QMaxShowPattern: Boolean
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() = LocalQMaxColors.current.showPattern
|
||||||
|
|
||||||
|
val QMaxIsDarkTheme: Boolean
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() = LocalQMaxColors.current.isDark
|
||||||
|
|
||||||
|
val QMaxChatTextStyle: TextStyle
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() {
|
||||||
|
val colors = LocalQMaxColors.current
|
||||||
|
return MaterialTheme.typography.bodyLarge.copy(
|
||||||
|
fontSize = colors.chatFontSizeSp.sp,
|
||||||
|
lineHeight = (colors.chatFontSizeSp + colors.chatLineSpacingSp).sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val QMaxUnread: Color
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() = LocalQMaxColors.current.accent
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun QMaxTheme(content: @Composable () -> Unit) {
|
fun QMaxTheme(
|
||||||
MaterialTheme(
|
appearance: AppearanceSettings = AppearanceSettings(),
|
||||||
colorScheme = Colors,
|
content: @Composable () -> Unit
|
||||||
typography = MaterialTheme.typography,
|
) {
|
||||||
content = content
|
val isDark = when (appearance.mode) {
|
||||||
|
ThemeMode.System -> isSystemInDarkTheme()
|
||||||
|
ThemeMode.Light -> false
|
||||||
|
ThemeMode.Dark -> true
|
||||||
|
}
|
||||||
|
val colors = colorsFor(
|
||||||
|
palette = appearance.palette(isDark),
|
||||||
|
isDark = isDark,
|
||||||
|
chatFontSizeSp = appearance.chatFontSizeSp,
|
||||||
|
chatLineSpacingSp = appearance.chatLineSpacingSp
|
||||||
|
)
|
||||||
|
val materialColors = materialColorScheme(colors)
|
||||||
|
|
||||||
|
CompositionLocalProvider(LocalQMaxColors provides colors) {
|
||||||
|
MaterialTheme(
|
||||||
|
colorScheme = materialColors,
|
||||||
|
typography = MaterialTheme.typography,
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun colorsFor(
|
||||||
|
palette: ChatPalette,
|
||||||
|
isDark: Boolean,
|
||||||
|
chatFontSizeSp: Float,
|
||||||
|
chatLineSpacingSp: Float
|
||||||
|
): QMaxThemeColors {
|
||||||
|
val accent = Color(palette.accent)
|
||||||
|
return QMaxThemeColors(
|
||||||
|
accent = accent,
|
||||||
|
onAccent = if (accent.luminance() > 0.48f) Color.Black else Color.White,
|
||||||
|
appBackground = if (isDark) Color(0xFF0E1621) else Color(0xFFF3F7FA),
|
||||||
|
surface = if (isDark) Color(0xFF17212B) else Color.White,
|
||||||
|
surfaceVariant = if (isDark) Color(0xFF232E3B) else Color(0xFFE9EEF2),
|
||||||
|
divider = if (isDark) Color(0xFF2A3948) else Color(0xFFE7EEF3),
|
||||||
|
text = if (isDark) Color(0xFFF5F7FA) else Color(0xFF17212B),
|
||||||
|
muted = if (isDark) Color(0xFFA7B3BF) else Color(0xFF6C7883),
|
||||||
|
chatBackground = Color(palette.chatBackground),
|
||||||
|
incomingBubble = Color(palette.incomingBubble),
|
||||||
|
outgoingBubble = Color(palette.outgoingBubble),
|
||||||
|
messageText = Color(palette.messageText),
|
||||||
|
pattern = Color(palette.patternColor),
|
||||||
|
showPattern = palette.showPattern,
|
||||||
|
isDark = isDark,
|
||||||
|
chatFontSizeSp = chatFontSizeSp,
|
||||||
|
chatLineSpacingSp = chatLineSpacingSp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun materialColorScheme(colors: QMaxThemeColors): ColorScheme {
|
||||||
|
return if (colors.isDark) {
|
||||||
|
darkColorScheme(
|
||||||
|
primary = colors.accent,
|
||||||
|
onPrimary = colors.onAccent,
|
||||||
|
background = colors.appBackground,
|
||||||
|
onBackground = colors.text,
|
||||||
|
surface = colors.surface,
|
||||||
|
onSurface = colors.text,
|
||||||
|
surfaceVariant = colors.surfaceVariant,
|
||||||
|
onSurfaceVariant = colors.muted,
|
||||||
|
outline = colors.divider
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
lightColorScheme(
|
||||||
|
primary = colors.accent,
|
||||||
|
onPrimary = colors.onAccent,
|
||||||
|
background = colors.appBackground,
|
||||||
|
onBackground = colors.text,
|
||||||
|
surface = colors.surface,
|
||||||
|
onSurface = colors.text,
|
||||||
|
surfaceVariant = colors.surfaceVariant,
|
||||||
|
onSurfaceVariant = colors.muted,
|
||||||
|
outline = colors.divider
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"slug": "qmax",
|
"slug": "qmax",
|
||||||
"name": "QMAX",
|
"name": "QMAX",
|
||||||
"version": "0.1.54",
|
"version": "1.0.0",
|
||||||
"androidVersionCode": 55,
|
"androidVersionCode": 57,
|
||||||
"channel": "stable",
|
"channel": "stable",
|
||||||
"platform": "android",
|
"platform": "android",
|
||||||
"packageKind": "apk",
|
"packageKind": "apk",
|
||||||
"downloadPath": "/api/app-updates/android/download/qmax-0.1.54-stable.apk",
|
"downloadPath": "/api/app-updates/android/download/qmax-1.0.0-stable.apk",
|
||||||
"packageSizeBytes": 17862069,
|
"packageSizeBytes": 17911217,
|
||||||
"sha256": "69a65b8c8d00c16f2ece211f1e993b0a2da46776f8afa436db384fb818c2f147",
|
"sha256": "f21d2f6ed8584ba204560c75fecae6d78041b20f620fb4ef992e11e6a230b5bb",
|
||||||
"notes": "QMAX 0.1.54",
|
"notes": "QMAX 1.0.0",
|
||||||
"publishedAt": "2026-07-13T19:13:39.6141836Z"
|
"publishedAt": "2026-07-15T09:12:25.7697332Z"
|
||||||
}
|
}
|
||||||
Binary file not shown.
Binary file not shown.
@@ -438,6 +438,7 @@ class PyMaxRuntime:
|
|||||||
@client.on_start()
|
@client.on_start()
|
||||||
async def on_start(c: Client) -> None:
|
async def on_start(c: Client) -> None:
|
||||||
self.client = c
|
self.client = c
|
||||||
|
self.last_error = None
|
||||||
me = getattr(c, "me", None)
|
me = getattr(c, "me", None)
|
||||||
self.last_title = str(getattr(me, "first_name", "") or getattr(me, "name", "") or "PyMax")
|
self.last_title = str(getattr(me, "first_name", "") or getattr(me, "name", "") or "PyMax")
|
||||||
self.last_started_at = utc_now()
|
self.last_started_at = utc_now()
|
||||||
|
|||||||
Reference in New Issue
Block a user