wear: M0 - scaffold :wear module with bitchat theme, verified on Pixel Watch 3

This commit is contained in:
callebtc 2026-07-28 15:54:41 +02:00
parent 11d0bd794e
commit a355ed79d0
16 changed files with 613 additions and 0 deletions

View File

@ -0,0 +1,241 @@
# Bitchat for Pixel Watch — Implementation Plan
> **Status tracker**: each milestone carries a status (`pending` / `in-progress` / `done`) and a
> checklist. A milestone may only be started when the previous milestone's success criteria pass.
> Update statuses in this file as work progresses.
| Milestone | Title | Status |
|-----------|-------|--------|
| M0 | Scaffolding & plan document | done |
| M1 | Shared core compiles on Wear | pending |
| M2 | BLE transport & background service on watch | pending |
| M3 | Global chat | pending |
| M4 | Noise DMs & people screen | pending |
| M5 | File/image receive & display — **DEFERRED** (post-M7, later day) | deferred |
| M6 | ADB test hook & mesh_lab interop | pending |
| M7 | Polish & final design pass | pending |
---
## 1. Context for a fresh coding agent
- **Reference app**: this repository (`bitchat-android`) is a fully working, decentralized BLE mesh
chat client. Single Gradle module `:app`, root package `com.bitchat.android`, applicationId
`com.bitchat.droid`. See `AGENTS.md` for the full architecture overview.
- **Goal**: a new `:wear` Gradle module (applicationId `com.bitchat.watch`) — a standalone Wear OS
app for the Pixel Watch. **Bluetooth mesh only**: global chat, Noise-encrypted direct messages,
and receiving/displaying files & images. It must be a fully interoperable bitchat client: scan,
advertise, connect, relay, handshake, and exchange messages with the Android (and iOS) apps.
- **Explicitly out of scope**: no internet features (no Nostr, no Tor/Arti, no relays), no GPS /
geohash / location channels, no Wi-Fi Aware, no hotspot/APK sharing, no voice notes recording.
The watch manifest must not even declare `INTERNET` or location permissions.
- **Hard constraint**: **zero modifications to `:app` production code.** The only allowed changes
to shared repo files are: `settings.gradle.kts` (add `include(":wear")`), entries in
`gradle/libs.versions.toml` (new wear dependencies only), the new `wear/` directory, and docs.
All shared Kotlin code is consumed by the `:wear` module *in place* via Gradle source sets —
files are never moved, copied, or edited.
## 2. Code reuse strategy (shared source sets)
Wear OS is Android. `android.util.Log`, `android.bluetooth.*`, `EncryptedSharedPreferences`
(androidx.security), BouncyCastle, and coroutines all work on the watch, so the vast majority of
the bitchat protocol stack compiles unmodified.
In `wear/build.gradle.kts`:
```kotlin
sourceSets["main"].java.srcDir("../app/src/main/java") // with include filters (see below)
```
**Include** (iteratively refined by fixing compile errors — the include list lives in
`wear/build.gradle.kts` with comments):
- `protocol/**` — wire format, `BinaryProtocol`, `CompressionUtil`, `MessagePadding`
- `model/**``BitchatMessage`, `BitchatFilePacket`, `FragmentPayload`, `NoiseEncrypted`,
`IdentityAnnouncement`, `RoutedPacket`
- `noise/**``NoiseSession`, `NoiseSessionManager`, `NoiseEncryptionService`,
`NoiseChannelEncryption`, vendored pure-Java `noise/southernstorm/**`
- `crypto/**``EncryptionService`
- `identity/**``SecureIdentityStateManager`
- `mesh/**` — BLE stack (`BluetoothConnectionManager`, GATT server/client managers, broadcaster,
tracker, permission manager), `FragmentManager`, `SecurityManager`, `PacketProcessor`,
`MessageHandler`, `PeerManager`, `StoreForwardManager`, `MeshTransport`, `MeshService`,
`TransferProgressManager`, `PrivateMediaTransfer`, `PowerManager`
- `services/AppStateStore.kt` — process-wide state store
- `util/AppConstants.kt` — shared constants (GATT UUIDs, fragmentation sizes)
- Small transitive deps the compiler reveals (known: `ui/debug/DebugSettingsManager.kt` is
referenced from the mesh layer — include the file, not the package)
**Exclude**: `ui/**` (except forced single-file includes), `onboarding/**`, `nostr/**`, `net/**`,
`geohash/**`, `wifi-aware/**`, `hotspot/**`, `features/voice/**`, `service/MeshForegroundService.kt`
(wear gets its own service), `BitchatApplication.kt`, `MainActivity.kt`.
**Tests**: the app's own unit tests for shared packages (`protocol`, `noise`, `crypto`, `mesh`)
are wired into the `:wear` test source set the same way (srcDir + includes), so shared behavior is
continuously verified on both modules.
**Resources**: font files cannot be selectively shared via srcDir cleanly — copy the 4 Geist Mono
font files (`app/src/main/res/font/geist_mono_*`) into `wear/src/main/res/font/`. Theme/palette/
peer-color logic is re-created as wear-owned files mirroring `ui/theme/` values exactly.
## 3. Watch UX design
Wear Compose Material3 (round-screen safe by default):
- **Screens**: Chat (global timeline) → People (connected peers w/ RSSI, unread badges) →
DM conversation. Edge-swipe back, `TimeText` scaffold, rotary crown scrolling.
- **Visual identity** (mirrors `ui/theme/` exactly): black background `#000000`, green primary
`#32D74B`, error `#FF453A`, orange accent for self/mentions, djb2-hash stable peer colors
(`PeerColors.kt` algorithm), Geist Mono typography, `BitchatMotion` timing tokens
(120/180/240 ms) for all animations.
- **Input**: text field using the Pixel Watch Gboard IME, plus voice dictation via
`RecognizerIntent`. Haptic feedback on incoming messages.
- **Background**: wear-owned foreground service (type `connectedDevice`) keeps scan/advertise
alive; shared `PowerManager` provides duty-cycling.
## 4. Hardware & test environment
- Pixel Watch connected via ADB (target device for all milestones; screencaps via
`adb exec-out screencap`).
- Two phones running the Android bitchat app, also on ADB, for interop testing (used heavily from
M6; manual interop checks from M2 onward).
- Design verification: at every UI milestone, take ADB screencaps of every screen and review them
for round-screen clipping, element visibility, contrast, and touch-target size. A milestone does
not pass until its screencap set is approved.
## 5. Risks & notes
- `BluetoothMeshService` (legacy monolith) vs `MeshCore` — prefer wiring the shared components
directly (MeshCore-style composition) in the wear service.
- `mesh/` references `ui/debug/DebugSettingsManager` — include that single file; do not pull in
the debug UI sheet.
- Wear BLE MTUs are small; the shared fragmentation layer (469-byte fragments) already handles
this.
- `EncryptedSharedPreferences` (androidx.security-crypto) works on Wear OS — identity persistence
is reused as-is.
- Watch has no camera/gallery: file transfer is **receive + display only** (confirmed decision).
---
## Milestones
### M0 — Scaffolding & plan document
- [x] Write this plan to `docs/wear-os-implementation-plan.md`
- [x] Create `:wear` module: `wear/build.gradle.kts`, manifest
(`<uses-feature android:name="android.hardware.type.watch"/>`, standalone, BT permissions,
**no INTERNET/location**), `MainActivity` with hello-world screen using the ported theme
- [x] Add Wear Compose dependencies to `gradle/libs.versions.toml`; `include(":wear")` in
`settings.gradle.kts`
- [x] Build, install, and launch on the physical Pixel Watch via ADB; take first screencap
**Success criteria**: `./gradlew :wear:assembleDebug` green; app launches on the watch;
`git diff --name-only` shows no changes under `app/src/`.
**Result**: PASSED — installed on Pixel Watch 3 (serial 4C201JEAYW0020), launch screencap shows
"bitchat" wordmark (green `#32D74B`, Geist Mono, black background) correctly centered on the
round display. No `app/src/` changes.
---
### M1 — Shared core compiles on Wear
- [ ] Configure srcDir include list in `wear/build.gradle.kts`; resolve transitive dependencies by
extending includes (never by copying Kotlin sources)
- [ ] Copy Geist Mono fonts; create wear theme/palette/peer-color files mirroring `ui/theme/`
- [ ] Wire shared unit tests (`protocol`, `noise`, `crypto`, `mesh`) into `:wear` test source set
- [ ] `./gradlew :app:test :wear:test` green
**Success criteria**: the entire shared stack (protocol, noise, crypto, identity, mesh, model,
AppStateStore) compiles into `:wear`; both modules' unit tests pass; `app/src/` untouched.
---
### M2 — BLE transport & background service on watch
- [ ] Wear onboarding flow: Bluetooth-enable check + runtime permission requests
(`BLUETOOTH_SCAN/CONNECT/ADVERTISE`), watch-styled screens
- [ ] `WearMeshService` foreground service (type `connectedDevice`); wire shared
`BluetoothConnectionManager` + mesh components; start scanning + advertising
- [ ] Internal debug screen: discovered peers with RSSI (temporary, replaced by real UI in M3/M4)
- [ ] Manual interop check: watch and one phone mutually discover
**Success criteria**: the phone's bitchat app lists the watch as a connected peer and vice versa
(logcat + screencap evidence); mesh survives the screen turning off (ambient mode) for 5 minutes.
---
### M3 — Global chat
- [ ] Nickname onboarding; identity announcement over the mesh
- [ ] Send/receive/relay public `BitchatMessage`s (relay/TTL comes free from shared mesh code)
- [ ] Chat timeline UI (`ScalingLazyColumn`, message bubbles per bitchat style, peer colors,
timestamps) + composer (IME + `RecognizerIntent` dictation) + incoming-message haptics
- [ ] Design check: ADB screencaps of onboarding, chat (empty/populated), composer; review for
round-screen clipping/visibility/contrast
**Success criteria**: two-way public chat between watch and phone; messages the watch relays reach
a second phone that is only connected through the first (relay proof); screencap set approved.
---
### M4 — Noise DMs & people screen
- [ ] People screen: connected peers, nicknames, RSSI, unread-DM badges
- [ ] Tap peer → Noise XX handshake (shared `EncryptionService`/`NoiseSessionManager`) → DM thread
- [ ] DM conversation UI; unread counters; delivery/read receipts if supported by shared code
- [ ] Identity persistence (`EncryptedSharedPreferences`); stale-session detection & automatic
re-handshake after watch app restart
- [ ] Design check: screencaps of people screen, handshake state, DM thread
**Success criteria**: encrypted DM round trip with the phone; DMs survive a watch app restart
(session recovery); screencap set approved.
---
### M5 — File/image receive & display — **DEFERRED**
> Deferred to a later day (after M7). Milestones M6 and M7 do not depend on M5 and proceed
> without it. The mesh_lab `file` scenario for the watch is skipped until M5 is un-deferred.
- [ ] Receive broadcast files (`MessageType.FILE_TRANSFER`, `BitchatFilePacket` TLV decode,
fragment reassembly — all shared)
- [ ] Receive Noise-encrypted private files (`NoisePayloadType.FILE_TRANSFER`)
- [ ] Inline image rendering in chat timelines; full-screen image viewer (pinch/crown zoom);
non-image files saved with a way to open/share them
- [ ] Transfer progress indicator; respect shared fragment/size caps
- [ ] Design check: screencaps of inline image, full-screen viewer, transfer progress
**Success criteria**: phone→watch image renders inline in both global chat and DM; SHA-256 of
received file matches sender; screencap set approved. (Sending files from the watch is out of
scope.)
---
### M6 — ADB test hook & mesh_lab interop
- [ ] Wear debug-only `TestHookReceiver` (`wear/src/debug/`) mirroring the phone's command set:
`ping`, `start`, `stop`, `whoami`, `set_nickname`, `scan`, `peers`, `connect`, `handshake`,
`session`, `announce`, `broadcast_msg`, `dm_send`, `dm_recv`, `msg_recv`, `file_recv`, `state`,
`clear_results` — broadcast action `com.bitchat.watch.TEST_HOOK`, same JSON-result-file protocol
- [ ] Extend `tools/release_gate/mesh_lab.py`: `--serial-watch` argument and phone↔watch scenarios
(`dm`, `broadcast`, `session_recovery`, `all`; `file` excluded while M5 is deferred), reusing
the existing `Device`/`cmd` machinery
- [ ] Run full scenario suite phone↔watch; store evidence JSON
**Success criteria**:
`python3 tools/release_gate/mesh_lab.py scenario all --serial-a <phone> --serial-watch <watch>`
exits 0 with evidence files; no manual intervention.
---
### M7 — Polish & final design pass
- [ ] Animations/transitions per `BitchatMotion` tokens; message-appear animations; screen
transitions; rotary scroll feel; splash screen & app icon (bitchat wordmark style)
- [ ] Power/battery: verify shared `PowerManager` duty-cycling behaves on Wear; ambient-mode
behavior; memory audit (fragment/image caps)
- [ ] Full screencap design review of every screen and state; fix all findings
- [ ] Update this document: all milestones `done`; add a short "how to build/run/test" section
**Success criteria**: all milestones marked `done`; interop suite green; final screencap set
approved; a fresh agent can build, install, and test the watch app from this document alone.

View File

@ -21,6 +21,10 @@ navigation-compose = "2.9.8"
# Accompanist
accompanist-permissions = "0.37.3"
# Wear OS
wear-compose = "1.6.2"
wear-tooling-preview = "1.0.0"
# Cryptography
bouncycastle = "1.85"
tink-android = "1.23.0"
@ -95,6 +99,11 @@ androidx-navigation-compose = { module = "androidx.navigation:navigation-compose
# Accompanist
accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist-permissions" }
# Wear OS
androidx-wear-compose-foundation = { module = "androidx.wear.compose:compose-foundation", version.ref = "wear-compose" }
androidx-wear-compose-material3 = { module = "androidx.wear.compose:compose-material3", version.ref = "wear-compose" }
androidx-wear-tooling-preview = { module = "androidx.wear:wear-tooling-preview", version.ref = "wear-tooling-preview" }
# Cryptography
bouncycastle-bcprov = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "bouncycastle" }
google-tink-android = { module = "com.google.crypto.tink:tink-android", version.ref = "tink-android" }

View File

@ -17,4 +17,5 @@ dependencyResolutionManagement {
rootProject.name = "bitchat-android"
include(":app")
include(":wear")
// Using published Arti AAR; local module not included

89
wear/build.gradle.kts Normal file
View File

@ -0,0 +1,89 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.parcelize)
alias(libs.plugins.kotlin.compose)
}
android {
namespace = "com.bitchat.watch"
compileSdk = libs.versions.compileSdk.get().toInt()
defaultConfig {
applicationId = "com.bitchat.watch"
minSdk = 30 // Wear OS 3 (Pixel Watch 1); BLE APIs match the phone app's usage
targetSdk = libs.versions.targetSdk.get().toInt()
versionCode = 1
versionName = "0.1.0"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
buildFeatures {
compose = true
buildConfig = true
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
lint {
abortOnError = false
checkReleaseBuilds = false
}
}
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.activity.compose)
// Wear Compose
implementation(libs.androidx.wear.compose.foundation)
implementation(libs.androidx.wear.compose.material3)
implementation(libs.androidx.wear.tooling.preview)
// Lifecycle
implementation(libs.bundles.lifecycle)
implementation(libs.androidx.lifecycle.process)
// Coroutines
implementation(libs.kotlinx.coroutines.android)
// Cryptography (shared Noise/encryption stack)
implementation(libs.bouncycastle.bcprov)
// JSON (BitchatMessage model)
implementation(libs.gson)
// Security preferences (Noise identity persistence)
implementation(libs.androidx.security.crypto)
// Testing
testImplementation(libs.bundles.testing)
debugImplementation(libs.androidx.compose.ui.tooling)
}

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-feature android:name="android.hardware.type.watch" />
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="true" />
<!-- Bluetooth mesh only. Deliberately NO android.permission.INTERNET and no location. -->
<uses-permission
android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation"
tools:targetApi="s" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.VIBRATE" />
<application
android:allowBackup="false"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@android:style/Theme.DeviceDefault">
<meta-data
android:name="com.google.android.wearable.standalone"
android:value="true" />
<activity
android:name=".MainActivity"
android:exported="true"
android:taskAffinity="">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,52 @@
package com.bitchat.watch
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.wear.compose.material3.MaterialTheme
import androidx.wear.compose.material3.Text
import com.bitchat.watch.ui.theme.BitchatWearTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BitchatWearTheme {
PlaceholderScreen()
}
}
}
}
@Composable
fun PlaceholderScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "bitchat",
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary
)
Text(
text = "mesh initializing",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 8.dp)
)
}
}

View File

@ -0,0 +1,38 @@
package com.bitchat.watch.ui.theme
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color
@Immutable
data class BitchatPalette(
val inputOutline: Color,
val inputOutlineFocused: Color,
val inputSurface: Color,
val inputSurfaceFocused: Color,
val inputButton: Color,
val textTertiary: Color,
val accentOrange: Color,
val accentPurple: Color,
val peerColors: PeerColorStyle,
)
val DarkBitchatPalette = BitchatPalette(
inputOutline = Color(0xFF333635),
inputOutlineFocused = Color(0xFF5A605D),
inputSurface = Color(0xFF0B0B0B),
inputSurfaceFocused = Color(0xFF151515),
inputButton = Color(0xFF1E1E1E),
textTertiary = Color(0xFF6B776B),
accentOrange = Color(0xFFFF9F0A),
accentPurple = Color(0xFFBF5AF2),
peerColors = PeerColorStyle.Dark,
)
val LocalBitchatPalette = staticCompositionLocalOf { DarkBitchatPalette }
object BitchatMotion {
const val QUICK_MS = 120
const val STANDARD_MS = 180
const val EMPHASIZED_MS = 240
}

View File

@ -0,0 +1,35 @@
package com.bitchat.watch.ui.theme
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import kotlin.math.abs
@Immutable
data class PeerColorStyle(
val saturation: Float,
val value: Float,
) {
companion object {
val Dark = PeerColorStyle(saturation = 0.55f, value = 0.82f)
}
}
fun colorForPeer(stableKey: String, palette: BitchatPalette): Color {
var hash = 5381UL
for (byte in stableKey.toByteArray()) {
hash = ((hash shl 5) + hash) + byte.toUByte().toULong()
}
var hue = (hash % 360UL).toDouble() / 360.0
val orange = 30.0 / 360.0
if (abs(hue - orange) < 0.05) {
hue = (hue + 0.12) % 1.0
}
val style = palette.peerColors
return Color.hsv(
hue = (hue * 360).toFloat(),
saturation = style.saturation,
value = style.value
)
}

View File

@ -0,0 +1,42 @@
package com.bitchat.watch.ui.theme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.graphics.Color
import androidx.wear.compose.material3.ColorScheme
import androidx.wear.compose.material3.MaterialTheme
val BitchatWearColorScheme = ColorScheme(
primary = Color(0xFF32D74B),
onPrimary = Color.Black,
primaryContainer = Color(0xFF163D1D),
onPrimaryContainer = Color(0xFFB8F5C1),
secondary = Color(0xFF0A84FF),
onSecondary = Color.Black,
secondaryContainer = Color(0xFF082E54),
onSecondaryContainer = Color(0xFFC2E0FF),
tertiary = Color(0xFFFF9F0A),
onTertiary = Color.Black,
background = Color(0xFF000000),
onBackground = Color(0xFFF5F5F5),
surfaceContainer = Color(0xFF0E150E),
surfaceContainerLow = Color(0xFF0B0B0B),
surfaceContainerHigh = Color(0xFF182118),
onSurface = Color(0xFFF5F5F5),
onSurfaceVariant = Color(0xFF9AA69A),
outline = Color(0xFF2A3A2A),
outlineVariant = Color(0xFF1C271C),
error = Color(0xFFFF453A),
onError = Color.Black,
)
@Composable
fun BitchatWearTheme(content: @Composable () -> Unit) {
CompositionLocalProvider(LocalBitchatPalette provides DarkBitchatPalette) {
MaterialTheme(
colorScheme = BitchatWearColorScheme,
typography = BitchatWearTypography,
content = content
)
}
}

View File

@ -0,0 +1,43 @@
package com.bitchat.watch.ui.theme
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import androidx.wear.compose.material3.Typography
import com.bitchat.watch.R
val BitchatFontFamily = FontFamily(
Font(R.font.geist_mono_regular, FontWeight.Normal),
Font(R.font.geist_mono_medium, FontWeight.Medium),
Font(R.font.geist_mono_semibold, FontWeight.SemiBold),
Font(R.font.geist_mono_bold, FontWeight.Bold),
)
val BitchatWearTypography = Typography(
defaultFontFamily = BitchatFontFamily,
)
object ChatVisualTokens {
val MessageBodyStyle = TextStyle(
fontFamily = BitchatFontFamily,
fontWeight = FontWeight.Normal,
fontSize = 13.sp,
lineHeight = 17.sp,
)
val SenderStyle = TextStyle(
fontFamily = BitchatFontFamily,
fontWeight = FontWeight.SemiBold,
fontSize = 13.sp,
lineHeight = 15.sp,
)
val SystemActionStyle = TextStyle(
fontFamily = BitchatFontFamily,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 14.sp,
)
}

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="#000000"
android:pathData="M0,0h48v48h-48z" />
<path
android:fillColor="#32D74B"
android:pathData="M10,14l10,10l-10,10v-6l5,-4l-5,-4z" />
<path
android:fillColor="#32D74B"
android:pathData="M22,32h16v4h-16z" />
</vector>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">bitchat</string>
</resources>