themed-styler 1.2.10

Client-side runtime styling engine for web and Android/iOS Native with theme support and Tailwind subset
Documentation
use crate::{bridge_common, version, State};
use android_logger::Config;
use jni::objects::{JClass, JString};
use jni::sys::jstring;
use jni::JNIEnv;
use log::{debug, error, LevelFilter};
use once_cell::sync::Lazy;
use std::sync::RwLock;

static STATE: Lazy<RwLock<Option<State>>> = Lazy::new(|| RwLock::new(None));

fn init_logger() {
    android_logger::init_once(
        Config::default()
            .with_max_level(LevelFilter::Trace)
            .with_tag("THEMED_STYLER_RUST"),
    );
}

fn jstring_to_string(env: &mut JNIEnv, input: JString) -> Option<String> {
    if input.is_null() {
        return None;
    }
    match env.get_string(&input) {
        Ok(v) => v.to_str().ok().map(|s| s.to_string()),
        Err(_) => None,
    }
}

fn new_jstring(env: &mut JNIEnv, value: &str) -> jstring {
    match env.new_string(value) {
        Ok(jstr) => jstr.into_raw(),
        Err(_) => match env.new_string("") {
            Ok(jstr) => jstr.into_raw(),
            Err(_) => std::ptr::null_mut(),
        },
    }
}

#[unsafe(no_mangle)]
pub extern "system" fn Java_com_relay_client_ThemedStylerModule_nativeInitialize(
    mut env: JNIEnv,
    _class: JClass,
    themes_json: JString,
    display_density: f32,
    scaled_density: f32,
) {
    init_logger();
    let themes = jstring_to_string(&mut env, themes_json).unwrap_or_else(|| "{}".to_string());
    let themes_input = bridge_common::parse_themes_json(&themes);
    let mut state = bridge_common::build_state(themes_input);
    state.display_density = display_density;
    state.scaled_density = scaled_density;

    debug!(
        "[nativeInitialize] density={} scaled={} current_theme={}",
        state.display_density, state.scaled_density, state.current_theme
    );

    let mut global_state = STATE.write().unwrap();
    *global_state = Some(state);
}

#[unsafe(no_mangle)]
pub extern "system" fn Java_com_relay_client_ThemedStylerModule_nativeGetAndroidStyles(
    mut env: JNIEnv,
    _class: JClass,
    selector: JString,
    class_name: JString,
) -> jstring {
    let selector_str = jstring_to_string(&mut env, selector).unwrap_or_else(|| "div".to_string());
    let class_name_str = jstring_to_string(&mut env, class_name).unwrap_or_else(|| String::new());

    // Split classes and add dot prefix (offloading work to Rust)
    let classes_vec: Vec<String> = class_name_str
        .split_whitespace()
        .filter(|s| !s.is_empty())
        .map(|s| format!(".{}", s))
        .collect();

    let state_lock = STATE.read().unwrap();
    let state = match &*state_lock {
        Some(s) => s,
        None => {
            error!("[nativeGetAndroidStyles] STATE not initialized! Call nativeInitialize first.");
            return new_jstring(&mut env, "{}");
        }
    };

    let styles = state.android_styles_for(&selector_str, &classes_vec);

    match serde_json::to_string(&styles) {
        Ok(json) => new_jstring(&mut env, &json),
        Err(_) => new_jstring(&mut env, "{}"),
    }
}

#[unsafe(no_mangle)]
pub extern "system" fn Java_com_relay_client_ThemedStylerModule_nativeProcessStyles(
    mut env: JNIEnv,
    _class: JClass,
    styles_json: JString,
) -> jstring {
    let styles_str = jstring_to_string(&mut env, styles_json).unwrap_or_else(|| "{}".to_string());
    let styles: indexmap::IndexMap<String, serde_json::Value> =
        serde_json::from_str(&styles_str).unwrap_or_default();

    let state_lock = STATE.read().unwrap();
    let state = match &*state_lock {
        Some(s) => s,
        None => return new_jstring(&mut env, "{}"),
    };

    let processed = state.process_styles(styles);
    let json = serde_json::to_string(&processed).unwrap_or_else(|_| "{}".to_string());
    new_jstring(&mut env, &json)
}

#[unsafe(no_mangle)]
pub extern "system" fn Java_com_relay_client_ThemedStylerModule_nativeGetVersion(
    mut env: JNIEnv,
    _class: JClass,
) -> jstring {
    new_jstring(&mut env, version())
}