Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ dependencies {
implementation(libs.androidx.compose.material.icons.extended)
implementation(libs.coil.compose)

// ★ Markdown Preview(追加)
implementation(libs.jeziellago.compose.markdown)

// Activity / Lifecycle / Navigation
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.lifecycle.runtime.compose)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,44 @@ import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import kotlinx.coroutines.launch

private const val DS_NAME = "settings"
private val Context.dataStore by preferencesDataStore(name = DS_NAME)

class SettingsRepository private constructor(
private val appContext: Context,
// ★ Changed: 複数行パラメータなので末尾カンマを付ける(ktlint/spotless 推奨)
) {
// キーは lowerCamel に(ktlint 対応)
// Repository内でStateFlowを維持するためのスコープ(アプリプロセスと同寿命の想定)
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

// キーは lower_snake_case(Preferencesのキー文字列)でOK。プロパティ名はlowerCamelでOK。
private object Keys {
val filterFolderIdKey = longPreferencesKey("filter_folder_id")
val lastQueryKey = stringPreferencesKey("last_query")

// ★ Migrated: GitHub Token を DataStore に保存(平文)
val githubTokenKey = stringPreferencesKey("github_token")
}

/** 現在のフォルダ絞り込みID(なければ null) */
val filterFolderId: Flow<Long?> =
appContext.dataStore.data.map { prefs: Preferences ->
prefs[Keys.filterFolderIdKey]
}
appContext.dataStore.data
.map { prefs: Preferences -> prefs[Keys.filterFolderIdKey] }
.distinctUntilChanged()

/** フォルダ絞り込みIDを保存(null で削除) */
suspend fun setFilterFolderId(id: Long?) {
appContext.dataStore.edit { prefs ->
// ★ Changed: 1行 if/else をブロックに展開(読みやすさルール)
if (id == null) {
prefs.remove(Keys.filterFolderIdKey)
} else {
Expand All @@ -49,55 +55,59 @@ class SettingsRepository private constructor(

/** 検索クエリ(空文字をデフォルトで返す) */
val lastQuery: Flow<String> =
appContext.dataStore.data.map { prefs -> prefs[Keys.lastQueryKey] ?: "" }
appContext.dataStore.data
.map { prefs -> prefs[Keys.lastQueryKey] ?: "" }
.distinctUntilChanged()

/** 検索クエリを保存 */
suspend fun setLastQuery(q: String) {
appContext.dataStore.edit { prefs -> prefs[Keys.lastQueryKey] = q }
appContext.dataStore.edit { prefs ->
prefs[Keys.lastQueryKey] = q
}
}

// ─────────────────────────────────────────────────────────────
// GitHub Token (EncryptedSharedPreferences)
// GitHub Token (DataStore Preferences) ※平文
// ─────────────────────────────────────────────────────────────
private val masterKey = MasterKey.Builder(appContext)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()

private val encryptedPrefs = EncryptedSharedPreferences.create(
appContext,
"secure_settings",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
)
private val _githubToken = MutableStateFlow("")
val githubToken: StateFlow<String> = _githubToken.asStateFlow()

private val _githubToken = MutableStateFlow(encryptedPrefs.getString("github_token", "") ?: "")
val githubToken = _githubToken.asStateFlow()
init {
// DataStore → StateFlow にブリッジ(呼び出し元の変更を避けるため)
scope.launch {
appContext.dataStore.data
.map { prefs -> prefs[Keys.githubTokenKey] ?: "" }
.distinctUntilChanged()
.collect { token ->
_githubToken.value = token
}
}
}

/** GitHub Token を保存(空文字も許容。空なら remove にしたい場合はここで分岐) */
suspend fun setGithubToken(token: String) {
withContext(Dispatchers.IO) {
encryptedPrefs.edit().putString("github_token", token).apply()
_githubToken.value = token
appContext.dataStore.edit { prefs ->
prefs[Keys.githubTokenKey] = token
}
// 即時反映(collectの反映を待たずUIが追従する)
_githubToken.value = token
}

// ─────────────────────────────────────────────────────────────
// デバッグ/テスト用途の全消し API(今は未使用)
// ─────────────────────────────────────────────────────────────
@Suppress("unused")
// ★ Kept: 将来用に残す(未使用警告は抑制)
suspend fun clearAll() {
appContext.dataStore.edit { it.clear() }
_githubToken.value = ""
}

companion object {
// ★ Changed: ktlint(property-naming) 対応のため INSTANCE → instance にリネーム
@Volatile
private var instance: SettingsRepository? = null

fun get(context: Context): SettingsRepository = // ★ Changed: '=' の行内にコメントを寄せる(改行位置を修正)
instance ?: synchronized(this) {
instance ?: SettingsRepository(context.applicationContext).also { instance = it }
}
fun get(context: Context): SettingsRepository = instance ?: synchronized(this) {
instance ?: SettingsRepository(context.applicationContext).also { instance = it }
}
}
}
4 changes: 3 additions & 1 deletion app/src/main/java/com/example/bugmemo/ui/NotesViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import com.example.bugmemo.data.prefs.SettingsRepository
import com.example.bugmemo.data.remote.GistRequest
import com.example.bugmemo.data.remote.GistService
import com.example.bugmemo.data.seedIfEmpty
import com.example.bugmemo.ui.utils.GistContentBuilder // ★追加
import com.example.bugmemo.ui.utils.GistContentBuilder
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
Expand All @@ -40,6 +40,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject

// ★追加(com.example.bugmemo.ui.utils.GistContentBuilder)

@HiltViewModel
class NotesViewModel @Inject constructor(
private val repo: NotesRepository,
Expand Down
Loading
Loading