package com.example.shiftalarm import android.os.Bundle import android.view.MenuItem import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.LinearLayoutManager import androidx.activity.enableEdgeToEdge import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import com.example.shiftalarm.databinding.ActivityNoticeBinding import java.io.BufferedReader import java.io.InputStreamReader import java.net.HttpURLConnection import java.net.URL class NoticeActivity : AppCompatActivity() { private lateinit var binding: ActivityNoticeBinding override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) binding = ActivityNoticeBinding.inflate(layoutInflater) setContentView(binding.root) ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets -> val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) insets } supportActionBar?.hide() binding.btnCloseNotice.setOnClickListener { finish() } binding.noticeRecyclerView.layoutManager = LinearLayoutManager(this) fetchChangelog() } private fun fetchChangelog() { // GitHub Raw URL with cache busting val baseUrl = "https://raw.githubusercontent.com/sanjeok77-tech/dakjaba-releases/main/CHANGELOG.md" val urlString = "$baseUrl?t=${System.currentTimeMillis()}" Thread { try { val url = URL(urlString) val connection = url.openConnection() as HttpURLConnection connection.connectTimeout = 5000 connection.readTimeout = 5000 connection.requestMethod = "GET" connection.useCaches = false if (connection.responseCode == 200) { val reader = BufferedReader(InputStreamReader(connection.inputStream)) val content = reader.use { it.readText() } runOnUiThread { val notices = parseChangelog(content) binding.noticeRecyclerView.adapter = NoticeAdapter(notices) } } else { throw Exception("Server returned ${connection.responseCode}") } } catch (e: Exception) { e.printStackTrace() runOnUiThread { // Fallback to local asset loadLocalChangelog() } } }.start() } private fun loadLocalChangelog() { try { val content = assets.open("CHANGELOG.md").bufferedReader().use { it.readText() } val notices = parseChangelog(content) binding.noticeRecyclerView.adapter = NoticeAdapter(notices) } catch (e: Exception) { val empty = listOf(NoticeItem("데이터 로드 실패", "", "변경사항을 불러올 수 없습니다.")) binding.noticeRecyclerView.adapter = NoticeAdapter(empty) } } private fun parseChangelog(content: String): List { val notices = mutableListOf() val lines = content.lines() var currentVersion = "" var currentDate = "" var currentBody = StringBuilder() for (line in lines) { val trimmed = line.trim() // Skip empty lines or horizontal rules (any amount of dashes) if (trimmed.isEmpty() || trimmed.matches(Regex("-{2,}"))) continue // Handle version headers like "## v0.7.3" or "## [0.7.3]" if (trimmed.startsWith("## v") || trimmed.startsWith("## [")) { // Save previous version if exists if (currentVersion.isNotEmpty() && currentBody.isNotBlank()) { notices.add(NoticeItem("v$currentVersion 업데이트 정보", currentDate, currentBody.toString().trim())) } // Parse new version (matches v0.7.3 or [0.7.3]) val versionMatch = Regex("v?([\\d.]+)").find(trimmed) currentVersion = versionMatch?.groupValues?.getOrNull(1) ?: "" val dateMatch = Regex("(\\d{4}-\\d{2}-\\d{2})").find(trimmed) currentDate = dateMatch?.groupValues?.getOrNull(1) ?: "" currentBody = StringBuilder() } else if (trimmed.startsWith("- **") || trimmed.startsWith("* **")) { // Content line with bold key val cleaned = trimmed .replace(Regex("^[-*]\\s*\\*\\*(.+?)\\*\\*:?\\s*"), "▸ $1: ") .replace("**", "") currentBody.appendLine(cleaned) } else if (trimmed.startsWith("-") || trimmed.startsWith("*")) { // Regular bullet point val cleaned = trimmed.replace(Regex("^[-*]\\s*"), "• ") if (cleaned.length > 2) currentBody.appendLine(cleaned) } } // Add last version if (currentVersion.isNotEmpty() && currentBody.isNotBlank()) { notices.add(NoticeItem("v$currentVersion 업데이트 정보", currentDate, currentBody.toString().trim())) } return notices.take(7) } }