Pith - wedge_android
wedge_android/app/src/main/java/com/vgmlr/wedge/WedgeViewModel.kt [5.9 kb]
Modified: 18:56:46 154 026 (20 Aug 026)
3 Days Ago
package com.vgmlr.wedge

import android.content.Context
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.sp
import androidx.core.content.edit
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay

class MainViewModel(
    val dao: NoteDao,
    val prefs: PreferenceManager,
    context: Context
) : ViewModel() {
    
    private val appContext = context.applicationContext
    private val syncPrefs = appContext.getSharedPreferences("sync_prefs", Context.MODE_PRIVATE)
    private val _syncStatus = MutableStateFlow("Disconnected")
    val syncStatus = _syncStatus.asStateFlow()

    private val _isLinkUp = MutableStateFlow(false)
    val isLinkUp = _isLinkUp.asStateFlow()

    private val _peerLastModified = MutableStateFlow(0L)

    private val _autoMerge = MutableStateFlow(syncPrefs.getBoolean("auto_merge", false))
    val autoMerge = _autoMerge.asStateFlow()
    private var idleJob: Job? = null
    
    // bump editor on sync
    private val _syncEvent = MutableStateFlow(0L)
    val syncEvent = _syncEvent.asStateFlow()

    private var currentPassKey = ""

    init {
        viewModelScope.launch {
            prefs.passKey.collect { currentPassKey = it }
        }
    }

    fun updatePassKey(newKey: String) {
        viewModelScope.launch {
            prefs.setPassKey(newKey)
        }
    }

    fun setAutoMerge(on: Boolean) {
        _autoMerge.value = on
        syncPrefs.edit { putBoolean("auto_merge", on) }
        peer.refreshMeta()
        autoMergeTick()
    }

    fun noteActivity() {
        if (!peer.autoMergeReady()) return
        idleJob?.cancel()
        idleJob = viewModelScope.launch {
            // real-time sync delay
            delay(3000)
            autoMergeTick()
        }
    }

    private fun autoMergeTick() {
        if (!peer.autoMergeReady()) return
        viewModelScope.launch(Dispatchers.IO) {
            val note = dao.getNoteSync()
            val local = note?.lastModified ?: 0L
            if (local <= _peerLastModified.value) return@launch 
            doSend(note?.content ?: "", local)
        }
    }

    private val peer = PeerManager(
        getLocalPassKey = { currentPassKey },
        getLocalLastModified = { dao.getNoteSync()?.lastModified ?: 0L },
        getAutoMerge = { _autoMerge.value },
        onPeerMeta = { peerDate -> _peerLastModified.value = peerDate },
        onMessageReceived = { cleanMessage, peerDate ->
            viewModelScope.launch(Dispatchers.IO) {
                val stamp = if (peerDate > 0L) peerDate else System.currentTimeMillis()
                dao.save(NoteEntity(id = 1, content = cleanMessage, lastModified = stamp))
                NoteWidgetProvider.triggerUpdate(appContext)
                syncPrefs.edit {
                    putLong("sync_last_stamp", System.currentTimeMillis())
                }
                withContext(Dispatchers.Main) {
                    _syncStatus.value = "Received"
                    _syncEvent.value = System.currentTimeMillis()
                    noteActivity()
                }
            }
        },
        onLinkReady = { autoMergeTick() },
        onStatusChanged = { status, isConnected ->
            viewModelScope.launch {
                _syncStatus.value = status
                _isLinkUp.value = isConnected
            }
        }
    )

    init {
        peer.startListener()
    }

    fun trySendNoteText(route: String, peerHost: String, onOlderThanDestination: () -> Unit) {
        viewModelScope.launch(Dispatchers.IO) {
            val host = if (route == "USB") "127.0.0.1" else peerHost
            if (!peer.dial(host, 35691)) return@launch
            val note = dao.getNoteSync()
            val localDate = note?.lastModified ?: 0L
            val peerDate = _peerLastModified.value
            if (peerDate != 0L && localDate < peerDate) {
                withContext(Dispatchers.Main) { onOlderThanDestination() }
            } else {
                doSend(note?.content ?: "", localDate)
            }
        }
    }

    fun confirmSendNoteText() {
        if (!peer.isLinkUp()) return
        viewModelScope.launch(Dispatchers.IO) {
            val note = dao.getNoteSync()
            doSend(note?.content ?: "", note?.lastModified ?: 0L)
        }
    }

    private fun doSend(text: String, date: Long) {
        peer.sendTextMessage(text, currentPassKey, date, onSuccess = {
            _syncStatus.value = "Sent"
        })
    }
    
    val editorStyle = prefs.settings.map { settings ->
        TextStyle(
            fontSize = settings.editorFontSize.sp,
            fontFamily = FontFamily.Monospace,
            lineHeight = (settings.editorFontSize * settings.lineHeight).sp,
            color = parseColor(settings.textColor)
        )
    }.stateIn(
        viewModelScope,
        SharingStarted.Eagerly,
        TextStyle(
            fontSize = WedgeConfig.FONT_SIZE_DEFAULT.sp,
            fontFamily = FontFamily.Monospace,
            lineHeight = (WedgeConfig.FONT_SIZE_DEFAULT * WedgeConfig.LINE_HEIGHT_DEFAULT).sp,
            color = parseColor(WedgeConfig.TEXT_COLOR_DEFAULT)
        )
    )

    override fun onCleared() {
        super.onCleared()
        peer.stop()
    }
}

class MainViewModelFactory(
    private val dao: NoteDao,
    private val prefs: PreferenceManager,
    private val context: Context
) : androidx.lifecycle.ViewModelProvider.Factory {
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return MainViewModel(dao, prefs, context) as T
    }
}
Updates
Kerf - Android 157.026
Kiln - Android 157.026
Wedge - Android 156.026
Whittle - Linux 155.026
Grit - Firefox/Chrome 154.026

Menu
Calendar
Project Tin (024/029)
Miter
RSS Feed
User Avatar
@vgmlr
=SUM(parts)
0.00209
256,855 (+109)