使用 Kotlin Compose Multiplatform 构建跨平台密码管理器

项目概述

在现代数字化生活中,我们拥有越来越多的在线账户,管理这些账户的密码成为一个重要需求。本文将介绍如何使用 Kotlin Compose Multiplatform 构建一个现代化的密码管理器应用 —— Caddy

项目源码:https://gitee.com/gomes/password-manager-kotlin

技术栈

核心技术

  • Kotlin 2.3.10 - 现代化的 JVM 编程语言
  • Compose Multiplatform 1.10.0 - JetBrains 推出的跨平台 UI 框架
  • Material Design 3 - Google 最新的设计规范
  • JDK 21 - 最新的 Java LTS 版本
  • Gradle 9.2.1 - 构建工具

为什么选择 Compose Multiplatform?

  1. 声明式 UI:使用 Kotlin 代码描述 UI,代码简洁易读
  2. 跨平台能力:一套代码可运行在 macOS、Windows、Linux、Android、iOS 等平台
  3. Material 3 支持:提供现代化的 UI 组件
  4. 状态管理:内置响应式状态管理机制
  5. 热重载:开发效率高

项目架构

整体架构

password-manager-kotlin/
├── build.gradle.kts          # 构建配置
├── settings.gradle.kts       # 项目设置
├── icon.icns                 # 应用图标
├── run.sh                    # 运行脚本
└── src/main/kotlin/passwordmanager/
    ├── Account.kt            # 数据模型
    ├── AccountRepository.kt  # 数据存储层
    ├── Main.kt               # 应用入口
    ├── MainScreen.kt         # 主界面
    └── AccountDialog.kt      # 对话框组件

分层设计

┌─────────────────────────────────────┐
│           UI Layer (Compose)         │
│  MainScreen.kt / AccountDialog.kt   │
├─────────────────────────────────────┤
│       Business Logic Layer           │
│         AccountRepository.kt         │
├─────────────────────────────────────┤
│         Data Model Layer             │
│             Account.kt               │
├─────────────────────────────────────┤
│       Persistence Layer              │
│    Properties File Storage           │
└─────────────────────────────────────┘

核心功能实现

1. 数据模型设计

使用 Kotlin 的 data class 定义账号实体,简洁且功能强大:

data class Account(
    var id: String = UUID.randomUUID().toString(),
    var website: String = "",
    var username: String = "",
    var password: String = "",
    var email: String = "",
    var notes: String = "",
    var createdAt: Long = System.currentTimeMillis(),
    var updatedAt: Long = System.currentTimeMillis()
)

设计亮点

  • 使用 UUID 自动生成唯一标识
  • 默认参数简化对象创建
  • 包含创建和更新时间戳
  • 不可变数据结构保证线程安全

2. 数据持久化

采用 Properties 文件存储,简单可靠:

class AccountRepository {
    private val dataFile: File
    private val accounts: MutableList<Account> = mutableListOf()
    
    init {
        val home = System.getProperty("user.home")
        dataFile = File(home, ".password-manager/accounts.properties")
        loadAccounts()
    }
    
    fun addAccount(account: Account) {
        accounts.add(account)
        saveAccounts()
    }
    
    fun searchAccounts(query: String?): List<Account> {
        if (query.isNullOrBlank()) return getAllAccounts()
        val lowerQuery = query.lowercase()
        return accounts.filter {
            it.website.lowercase().contains(lowerQuery) ||
            it.username.lowercase().contains(lowerQuery) ||
            it.email.lowercase().contains(lowerQuery)
        }
    }
}

存储格式

ids=uuid1,uuid2,uuid3
account.uuid1.website=example.com
account.uuid1.username=user1
account.uuid1.password=pass123

优点

  • 轻量级,无需数据库依赖
  • 人类可读,便于调试
  • 跨平台兼容
  • 易于备份和迁移

3. UI 组件设计

主界面 (MainScreen)

使用 Compose 的声明式语法构建界面:

@Composable
fun MainScreen(repository: AccountRepository) {
    var accounts by remember { mutableStateOf(repository.getAllAccounts()) }
    var searchText by remember { mutableStateOf("") }
    var selectedAccount by remember { mutableStateOf<Account?>(null) }
    
    MaterialTheme(
        colorScheme = lightColorScheme(
            primary = AccentColor,
            background = BgColor,
            surface = CardColor
        )
    ) {
        Column(modifier = Modifier.fillMaxSize()) {
            // 搜索栏
            OutlinedTextField(
                value = searchText,
                onValueChange = { searchText = it },
                placeholder = { Text("搜索账号...") }
            )
            
            // 账号列表
            LazyColumn {
                items(accounts) { account ->
                    AccountItem(account)
                }
            }
        }
    }
}
对话框组件
@Composable
fun AccountDialog(
    account: Account?,
    onDismiss: () -> Unit,
    onSave: (Account) -> Unit
) {
    Dialog(onDismissRequest = onDismiss) {
        Surface(
            modifier = Modifier.width(480.dp),
            shape = RoundedCornerShape(16.dp)
        ) {
            Column(modifier = Modifier.padding(24.dp)) {
                // 表单字段
                OutlinedTextField(
                    value = website,
                    onValueChange = { website = it },
                    label = { Text("网站 *") }
                )
                
                // 密码字段(支持显示/隐藏)
                OutlinedTextField(
                    value = password,
                    visualTransformation = if (passwordVisible) 
                        VisualTransformation.None 
                    else 
                        PasswordVisualTransformation()
                )
            }
        }
    }
}

4. 状态管理

Compose 的响应式状态管理:

// 使用 remember 保存状态
var accounts by remember { mutableStateOf(repository.getAllAccounts()) }

// 使用 derivedStateOf 计算派生状态
val totalPages by remember(filteredAccounts) { 
    mutableStateOf(maxOf(1, (filteredAccounts.size + pageSize - 1) / pageSize))
}

// 分页数据
val paginatedAccounts = remember(filteredAccounts, currentPage) {
    val start = (currentPage - 1) * pageSize
    val end = minOf(start + pageSize, filteredAccounts.size)
    if (start < filteredAccounts.size) filteredAccounts.subList(start, end) 
    else emptyList()
}

UI 设计

配色方案

private val AccentColor = Color(0xFF5AC8FA)      // 淡蓝色主色调
private val BgColor = Color(0xFFF6F6F6)          // 浅灰背景
private val CardColor = Color.White               // 卡片白色
private val BorderColor = Color(0xFFDCDCDC)      // 边框灰色
private val TextColor = Color(0xFF333333)        // 深色文字
private val SecondaryTextColor = Color(0xFF8E8E93) // 次要文字

功能特性

  1. 账号列表:支持分页显示,每页 10 条记录
  2. 搜索功能:支持网站名、用户名、邮箱模糊搜索
  3. CRUD 操作:添加、编辑、删除账号
  4. 密码复制:一键复制密码到剪贴板
  5. 密码显示/隐藏:对话框中可切换密码可见性

构建与运行

构建配置

// build.gradle.kts
plugins {
    kotlin("jvm") version "2.3.10"
    id("org.jetbrains.compose") version "1.10.0"
    id("org.jetbrains.kotlin.plugin.compose") version "2.3.10"
}

compose.desktop {
    application {
        mainClass = "passwordmanager.MainKt"
        nativeDistributions {
            targetFormats(TargetFormat.Dmg, TargetFormat.Pkg)
            packageName = "Caddy"
            packageVersion = "1.0.0"
            macOS {
                iconFile.set(file("icon.icns"))
            }
        }
    }
}

运行命令

# 开发运行
./gradlew run

# 构建 JAR
./gradlew build

# 打包 macOS 应用
./gradlew packageDmg
./gradlew packagePkg

性能优化

1. 懒加载列表

使用 LazyColumn 实现虚拟滚动,只渲染可见项:

LazyColumn {
    items(paginatedAccounts) { account ->
        AccountItem(account)
    }
}

2. 状态派生

使用 remember 缓存计算结果,避免重复计算:

val paginatedAccounts = remember(filteredAccounts, currentPage) {
    // 只在 filteredAccounts 或 currentPage 变化时重新计算
}

3. 按需刷新

只在数据变化时刷新列表:

fun refreshAccounts() {
    accounts = repository.getAllAccounts()
    filteredAccounts = repository.searchAccounts(searchText)
}

安全考虑

当前实现

  • 本地文件存储,数据不离开设备
  • 密码字段默认隐藏显示

改进建议

  1. 加密存储:使用 AES 加密密码数据
  2. 主密码:添加主密码验证
  3. 自动锁定:闲置一段时间后自动锁定
  4. 剪贴板清理:复制后定时清理剪贴板
  5. 安全输入:防止截屏和录屏

跨平台展望

当前项目专注于桌面端(macOS),但 Compose Multiplatform 的优势在于:

Android 支持

// androidApp/build.gradle.kts
android {
    namespace = "com.caddy.android"
    compileSdk = 34
}

iOS 支持(实验性)

// iosApp/iosApp.swift
ComposeView()
    .ignoresSafeArea(.all)

项目统计

  • 代码行数:约 608 行 Kotlin 代码
  • 文件数量:5 个 Kotlin 源文件
  • 构建时间:首次约 3 分钟,后续约 10 秒

总结

本项目展示了如何使用 Kotlin Compose Multiplatform 构建一个现代化的桌面应用:

  1. 技术选型:Compose Multiplatform 提供了声明式 UI 和跨平台能力
  2. 架构设计:清晰的分层结构,便于维护和扩展
  3. 数据存储:使用 Properties 文件简单可靠
  4. UI 设计:遵循 Material Design 3 规范,界面简洁美观
  5. 性能优化:懒加载和状态派生确保流畅体验

未来规划

  • 添加密码加密功能
  • 支持密码生成器
  • 添加分类管理
  • 支持导入导出
  • Android/iOS 客户端
  • 云同步功能

参考资源


作者:gomes
项目地址:https://gitee.com/gomes/password-manager-kotlin
创建日期:2026年3月11日

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐