キャラセリフ稼働可能

This commit is contained in:
2026-01-10 19:50:26 +09:00
parent 517f378388
commit 0d2c64b64c
22 changed files with 593 additions and 238 deletions

View File

@@ -1,24 +1,52 @@
package com.example.curation_train_app
import org.json.JSONObject
import org.json.JSONArray
object AiReplyParser {
fun parse(json: String): AiReply {
fun extractTextFromResponse(json: String): String {
return try {
// 配列としてパース
val arr = JSONObject("{\"a\":$json}").getJSONArray("a")
val obj = arr.getJSONObject(0)
val obj = JSONObject(json)
// textだけ取得
var text = obj.getString("text")
// GPT から返ってくるのは「text」だけ
val text = obj.optString("text", "(解析エラー)")
// ---- 余計なものを完全除去 ----
text = text
.replace("text\":", "") // "text":
.replace("\"text\":\"", "") // "text":"
.replace("{\"text\":\"", "") // {"text":"
.replace("\"}", "") // "}
.replace("}", "") // }
.replace("{", "") // {
.replace("\\n", "") // 改行タグ
.replace("\n", "")
.replace("emotion", "") // emotion行
.replace("normal", "")
.replace("happy", "")
.replace("angry", "")
.replace("surprised", "")
.replace("confused", "")
.replace("sleepy", "")
.replace("worry", "")
.replace("think", "")
.replace("", "")
.replace("", "")
.replace("\"", "")
.replace(",", "")
.replace(":", "")
// emotion は入っていないため、常に NORMAL を返す
val emotion = EmotionType.NORMAL
.trim()
AiReply(text, emotion)
text
} catch (e: Exception) {
AiReply("(JSON解析エラー)", EmotionType.NORMAL)
json
}
}
}

View File

@@ -20,4 +20,7 @@ class AkaneResponder : CharacterResponder {
"なんか気になる情報があるよねっ!気をつけていこーっ!"
}
}
fun reply(text: String): String = respond(text, InfoType.NORMAL)
}

View File

@@ -25,19 +25,15 @@ class CharacterReplyManager {
?: return "キャラが見つかりません。"
return responder.respond(info, type)
}
fun replyWithAI(characterId: String, info: String, type: InfoType): AiReply {
fun replyWithAI(characterId: String, info: String, type: InfoType): String {
val character = CharacterProfiles.getProfile(characterId)
?: return AiReply("(エラー:キャラが不明です)", EmotionType.NORMAL)
?: return "(エラー:キャラが不明です)"
val prompt = PromptBuilder.buildPrompt(character, info, type)
val ai = AiClient(apiKey = API_KEY)
val raw = ai.requestCharacterReply(prompt)
// JSON パース
return AiReplyParser.parse(raw)
val ai = AiClient("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")
return ai.requestCharacterReply(prompt)
}
}

View File

@@ -4,14 +4,171 @@ object DummyLines {
fun linesOf(region: String): List<String> {
return when(region) {
"北海道" -> listOf("JR北海道全線", "札幌市営地下鉄", "道南いさりび鉄道")
"東北" -> listOf("JR東日本東北", "仙台市地下鉄", "青い森鉄道")
"関東" -> listOf("JR東日本関東", "東京メトロ", "京急", "東急", "小田急", "京王")
"中部" -> listOf("JR東海", "名鉄", "近鉄(名古屋)")
"関西" -> listOf("JR西日本関西", "大阪メトロ", "阪急", "阪神", "京阪", "南海", "近鉄(関西)")
"中国" -> listOf("JR西日本中国", "広島電鉄", "一畑電車")
"四国" -> listOf("JR四国")
"九州" -> listOf("JR九州", "西鉄")
// -------------------------
// 北海道
// -------------------------
"hokkaido" -> listOf(
"JR北海道函館本線",
"JR北海道千歳線",
"JR北海道札沼線・学園都市線",
"JR北海道根室本線",
"JR北海道宗谷本線",
"札幌市営地下鉄 南北線",
"札幌市営地下鉄 東西線",
"札幌市営地下鉄 東豊線"
)
// -------------------------
// 東北
// -------------------------
"tohoku" -> listOf(
"JR東日本東北本線",
"JR東日本常磐線",
"JR東日本奥羽本線",
"JR東日本仙山線",
"JR東日本磐越西線",
"仙台市地下鉄 南北線",
"仙台市地下鉄 東西線"
)
// -------------------------
// 関東
// -------------------------
"kanto" -> listOf(
// JR
"JR東日本山手線",
"JR東日本中央線快速",
"JR東日本総武線各停",
"JR東日本京浜東北線",
"JR東日本常磐線",
"JR東日本埼京線",
"JR東日本横須賀線",
"JR東日本湘南新宿ライン",
"JR東日本京葉線",
"JR東日本武蔵野線",
// 私鉄
"東京メトロ(丸ノ内線)",
"東京メトロ(東西線)",
"東京メトロ(日比谷線)",
"東京メトロ(半蔵門線)",
"東京メトロ(南北線)",
"都営地下鉄(浅草線)",
"都営地下鉄(三田線)",
"東武東上線",
"東武伊勢崎線(スカイツリーライン)",
"西武池袋線",
"西武新宿線",
"小田急小田原線",
"京王京王線",
"東急東横線",
"東急田園都市線",
"京急本線",
"相鉄本線",
"つくばエクスプレス"
)
// -------------------------
// 中部(東海)
// -------------------------
"chubu" -> listOf(
// JR
"JR東海東海道本線 静岡)",
"JR東海東海道本線 名古屋)",
"JR東海中央本線",
"JR東海関西本線",
"JR東海高山本線",
"JR東海御殿場線",
// 名古屋の私鉄
"名古屋市営地下鉄 東山線",
"名古屋市営地下鉄 名城線",
"名古屋市営地下鉄 桜通線",
"名鉄名古屋本線",
"名鉄犬山線",
"名鉄常滑線",
"近鉄名古屋線"
)
// -------------------------
// 関西(あなたの本命)
// -------------------------
"kansai" -> listOf(
// JR西日本
"JR西日本阪和線",
"JR西日本大阪環状線",
"JR西日本大和路線",
"JR西日本関空・紀州路快速",
"JR西日本京都線",
"JR西日本神戸線",
"JR西日本宝塚線",
"JR西日本琵琶湖線",
// 南海
"南海本線",
"南海空港線",
"南海高野線",
"南海加太線",
// 泉北高速
"泉北高速鉄道線",
// Osaka Metro
"Osaka Metro 御堂筋線",
"Osaka Metro 四つ橋線",
"Osaka Metro 谷町線",
"Osaka Metro 堺筋線",
// 私鉄大手
"近鉄奈良線",
"近鉄大阪線",
"京阪本線",
"阪急京都線",
"阪急神戸線",
"阪急宝塚線",
"阪神本線",
"阪神なんば線"
)
// -------------------------
// 中国
// -------------------------
"chugoku" -> listOf(
"JR西日本山陽本線",
"JR西日本可部線",
"JR西日本伯備線",
"広島電鉄(市内線)",
"岡山電気軌道",
"一畑電車"
)
// -------------------------
// 四国
// -------------------------
"shikoku" -> listOf(
"JR四国予讃線",
"JR四国高徳線",
"JR四国徳島線",
"JR四国土讃線"
)
// -------------------------
// 九州
// -------------------------
"kyushu" -> listOf(
"JR九州鹿児島本線",
"JR九州長崎本線",
"JR九州日豊本線",
"JR九州久大本線",
"福岡市地下鉄 空港線",
"福岡市地下鉄 箱崎線",
"福岡市地下鉄 七隈線",
"西鉄天神大牟田線"
)
else -> listOf("該当する路線がありません")
}
}

View File

@@ -14,4 +14,15 @@ object FollowSettings {
val prefs = context.getSharedPreferences("follow", Context.MODE_PRIVATE)
prefs.edit().putStringSet("lines", lines.toSet()).apply()
}
fun loadNotifySettings(context: Context): Set<String> {
val prefs = context.getSharedPreferences("follow", Context.MODE_PRIVATE)
return prefs.getStringSet("notify", emptySet()) ?: emptySet()
}
fun saveNotifySettings(context: Context, types: Set<String>) {
val prefs = context.getSharedPreferences("follow", Context.MODE_PRIVATE)
prefs.edit().putStringSet("notify", types).apply()
}
}

View File

@@ -20,4 +20,6 @@ class HiyoriResponder : CharacterResponder {
"気になったことがあるの?よかったら教えてね。いっしょに見てみるよ。"
}
}
fun reply(text: String): String = respond(text, InfoType.NORMAL)
}

View File

@@ -8,6 +8,8 @@ enum class InfoType {
EVENT, // イベント・臨時列車
WEATHER, // 天候影響
NEW_TRAIN, // 新型車両関連
OTHER // 分類できない場合
OTHER, // 分類できない場合
NORMAL
}

View File

@@ -5,6 +5,7 @@ import android.widget.TextView
import androidx.activity.ComponentActivity
import android.content.Intent
import android.widget.Button
import android.widget.CheckBox
class LinePriorityActivity : ComponentActivity() {
@@ -34,6 +35,20 @@ class LinePriorityActivity : ComponentActivity() {
val boxes = listOf(box1, box2, box3, box4, box5, box6)
val saved = FollowSettings.loadNotifySettings(this)
val cbDelay = findViewById<CheckBox>(R.id.checkboxDelay)
val cbOuting = findViewById<CheckBox>(R.id.checkboxOuting)
val cbCampaign = findViewById<CheckBox>(R.id.checkboxCampaign)
val cbOther = findViewById<CheckBox>(R.id.checkboxOther)
// 保存済みの状態を反映
cbDelay.isChecked = saved.contains("delay")
cbOuting.isChecked = saved.contains("outing")
cbCampaign.isChecked = saved.contains("campaign")
cbOther.isChecked = saved.contains("other")
// ▼▼ 空き枠を探して路線名をセット ▼▼
for (box in boxes) {
val t = box.text.toString()
@@ -49,4 +64,25 @@ class LinePriorityActivity : ComponentActivity() {
}
// ▲▲ 空き枠を探してセット ▲▲
}
override fun onPause() {
super.onPause()
val selected = mutableSetOf<String>()
if (findViewById<CheckBox>(R.id.checkboxDelay).isChecked)
selected.add("delay")
if (findViewById<CheckBox>(R.id.checkboxOuting).isChecked)
selected.add("outing")
if (findViewById<CheckBox>(R.id.checkboxCampaign).isChecked)
selected.add("campaign")
if (findViewById<CheckBox>(R.id.checkboxOther).isChecked)
selected.add("other")
FollowSettings.saveNotifySettings(this, selected)
}
}

View File

@@ -2,6 +2,7 @@ package com.example.curation_train_app
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.activity.ComponentActivity
import androidx.recyclerview.widget.LinearLayoutManager
@@ -13,6 +14,8 @@ class LineSelectActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_line_select)
// ★ 地域名を受け取る
val region = intent.getStringExtra("region") ?: "未指定"
@@ -31,6 +34,19 @@ class LineSelectActivity : ComponentActivity() {
}
}
private fun onLineSelected(id: String) {
val lines = FollowSettings.loadLines(this).toMutableList()
if (!lines.contains(id)) {
lines.add(id)
FollowSettings.saveLines(this, lines)
}
finish()
}
// 優先度設定画面へ遷移
private fun openPriorityScreen(line: String) {
val intent = Intent(this, LinePriorityActivity::class.java)

View File

@@ -15,126 +15,40 @@ import retrofit2.Retrofit
import retrofit2.converter.scalars.ScalarsConverterFactory
import retrofit2.http.GET
import retrofit2.Call
import org.json.JSONObject
class MainActivity : ComponentActivity() {
private var lastTrafficStatus: String = ""
private var rssList: List<NewsItem> = emptyList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// --- キャラ初期画像 ---
val charId = CharacterSettings.loadCharacter(this)
val imageView = findViewById<ImageView>(R.id.imageCharacter)
setInitialCharacterImage()
val drawableId = when (charId) {
"reimu" -> R.drawable.reimu_normal
"marisa" -> R.drawable.marisa_normal
"flandre" -> R.drawable.flandre_normal
"sanae" -> R.drawable.sanae_normal
"akane" -> R.drawable.akane_normal
"momoka" -> R.drawable.momoka_normal
"sayaka" -> R.drawable.sayaka_normal
"hiyori" -> R.drawable.hiyori_normal
else -> R.drawable.reimu_normal
}
imageView.setImageResource(drawableId)
// ----- キャラ応答ボタン -----
val btn = findViewById<Button>(R.id.btnTestReply)
val inputText = findViewById<EditText>(R.id.inputText)
val commentView = findViewById<TextView>(R.id.textCharacterComment)
btn.setOnClickListener {
val text = inputText.text.toString()
val charId = CharacterSettings.loadCharacter(this)
val infoType = InfoClassifier.classify(text)
Thread {
val result = CharacterReplyManager().replyWithAI(charId, text, infoType)
runOnUiThread {
commentView.text = result.text
updateCharacterExpression(charId, result.emotion)
}
}.start()
}
// --- 運行情報 & RSS 読み込み ---
// --- 運行情報 & RSS ---
loadTraffic()
loadRss()
// --- RecyclerView 初期設定 ---
// RecyclerView
val recyclerNews = findViewById<RecyclerView>(R.id.recyclerNews)
recyclerNews.layoutManager = LinearLayoutManager(this)
// 路線設定画面へ遷移
val btnOpenRegion = findViewById<TextView>(R.id.textFavorite)
btnOpenRegion.setOnClickListener {
val intent = Intent(this, RegionSelectActivity::class.java)
startActivity(intent)
// 路線設定
findViewById<TextView>(R.id.textFavorite).setOnClickListener {
startActivity(Intent(this, RegionSelectActivity::class.java))
}
applyCharacterLayoutDefault()
// 詳細画面へ遷移
val titleView = findViewById<TextView>(R.id.textTitle)
titleView.setOnClickListener {
val text = titleView.tag as? String ?: "(詳細情報なし)"
val intent = Intent(this, TrafficDetailActivity::class.java)
intent.putExtra("text", text)
startActivity(intent)
}
}
// -----------------------------
private fun applyCharacterLayoutDefault() {
val img = findViewById<ImageView>(R.id.imageCharacter)
val scale = resources.displayMetrics.density
val widthDp = 110
val heightDp = 110
val offsetXdp = -8
val offsetYdp = -18
img.layoutParams.width = (widthDp * scale).toInt()
img.layoutParams.height = (heightDp * scale).toInt()
val lp = img.layoutParams as ViewGroup.MarginLayoutParams
lp.marginStart = (offsetXdp * scale).toInt()
lp.topMargin = (offsetYdp * scale).toInt()
img.layoutParams = lp
}
// -----------------------------
private fun loadRss() {
Thread {
try {
val api = RssApi()
val feed = api.loadRss()
val list = feed?.channel?.items?.map { item ->
NewsItem(
company = "JR西日本",
title = item.title ?: "",
body = item.description ?: ""
)
} ?: emptyList()
runOnUiThread {
val recyclerNews = findViewById<RecyclerView>(R.id.recyclerNews)
recyclerNews.adapter = NewsAdapter(list)
}
} catch (e: Exception) {
e.printStackTrace()
}
}.start()
}
// -----------------------------
// =========================================================
// 運行情報ロード
// =========================================================
private fun loadTraffic() {
Thread {
try {
@@ -144,27 +58,19 @@ class MainActivity : ComponentActivity() {
.build()
val client = retrofit.create(HtmlClient::class.java)
val response = client.getHtml().execute()
val html = response.body()?.string() ?: return@Thread
val html = client.getHtml().execute().body()?.string() ?: return@Thread
val doc = Jsoup.parse(html)
val lines = doc.select("div.jisyo")
var hasDelay = false
var hasSuspension = false
var hasInfo = false
val items = mutableListOf<NewsItem>()
val lines = doc.select("div.jisyo")
for (line in lines) {
val title = line.select("h2").text()
val body = line.select("p.gaiyo").text()
val classes = line.classNames()
if (classes.contains("stop")) hasSuspension = true
if (classes.contains("delay")) hasDelay = true
if (classes.contains("info")) hasInfo = true
val c = line.classNames()
if (c.contains("stop")) hasSuspension = true
if (c.contains("delay")) hasDelay = true
if (c.contains("info")) hasInfo = true
}
val statusText = when {
@@ -174,11 +80,28 @@ class MainActivity : ComponentActivity() {
else -> "平常運転"
}
lastTrafficStatus = statusText
val infoText = "現在の運行状況:$statusText"
// UI 反映
runOnUiThread {
val statusView = findViewById<TextView>(R.id.trafficStatus)
statusView.text = "設定した路線:$statusText"
findViewById<TextView>(R.id.trafficStatus).text =
"設定した路線:$statusText"
}
// ===== 平常時は RSS の情報からコメント生成 =====
if (lastTrafficStatus == "平常運転") {
if (rssList.isNotEmpty()) {
val importantItem = pickImportantRssItem(rssList)
if (importantItem != null) {
val infoText = "${importantItem.title}\n${importantItem.body}"
generateAutoCommentFromInfo(infoText)
}
}
} else {
// 遅延・見合わせは優先
generateAutoCommentFromInfo(infoText)
}
} catch (e: Exception) {
@@ -187,33 +110,42 @@ class MainActivity : ComponentActivity() {
}.start()
}
// -----------------------------
// =========================================================
// RSSロード
// =========================================================
private fun loadRss() {
Thread {
try {
val api = RssApi()
val feed = api.loadRss()
fun emotionToExpression(type: EmotionType): String {
return when (type) {
EmotionType.HAPPY -> "happy"
EmotionType.ANGRY -> "angry"
EmotionType.SLEEPY -> "sleepy"
EmotionType.SURPRISED -> "surprised"
EmotionType.CONFUSED -> "confused"
EmotionType.THINK -> "think"
EmotionType.WORRY -> "worry"
else -> "normal"
rssList = feed?.channel?.items?.map { item ->
NewsItem(
company = "JR西日本",
title = item.title ?: "",
body = item.description ?: ""
)
} ?: emptyList()
runOnUiThread {
val recycler = findViewById<RecyclerView>(R.id.recyclerNews)
recycler.adapter = NewsAdapter(rssList)
}
// 平常運転のとき → RSSの重要情報でコメント生成
if (lastTrafficStatus == "平常運転" && rssList.isNotEmpty()) {
val importantItem = pickImportantRssItem(rssList)
if (importantItem != null) {
val infoText = "${importantItem.title}\n${importantItem.body}"
generateAutoCommentFromInfo(infoText)
}
}
fun updateCharacterExpression(charId: String, type: EmotionType) {
val imageView = findViewById<ImageView>(R.id.imageCharacter)
val expression = emotionToExpression(type)
val resName = "${charId}_${expression}"
val resId = resources.getIdentifier(resName, "drawable", packageName)
if (resId != 0)
imageView.setImageResource(resId)
else {
val normalId = resources.getIdentifier("${charId}_normal", "drawable", packageName)
imageView.setImageResource(normalId)
} catch (e: Exception) {
e.printStackTrace()
}
}.start()
}
override fun onResume() {
@@ -235,7 +167,123 @@ class MainActivity : ComponentActivity() {
}
imageView.setImageResource(drawableId)
// ---- キャラ切り替え後、自動で最新コメントを再生成 ----
// ---- キャラ切り替え後、自動で最新コメントを再生成 ----
if (lastTrafficStatus == "平常運転" && rssList.isNotEmpty()) {
// ★ここを変更(ランダム or 後でフィルタできるように)
val item = pickRandomRssItem(rssList)
if (item != null) {
val infoText = "${item.title}\n${item.body}"
generateAutoCommentFromInfo(infoText)
}
} else {
// 遅延・運転見合わせならその情報を優先
val infoText = "現在の運行状況: $lastTrafficStatus"
generateAutoCommentFromInfo(infoText)
}
}
private fun classifyRssType(title: String, body: String): String {
return when {
title.contains("遅れ") || title.contains("運転") -> "delay"
title.contains("イベント") || body.contains("イベント") -> "outing"
title.contains("キャンペーン") -> "campaign"
else -> "other"
}
}
private fun updateExpressionByKeyword(text: String) {
val imageView = findViewById<ImageView>(R.id.imageCharacter)
val charId = CharacterSettings.loadCharacter(this)
val isDelay = text.contains("遅延") || text.contains("遅れ")
val isStop = text.contains("運転見合わせ") || text.contains("運休")
val expression = when {
isStop -> "angry" // 運転見合わせ=怒り
isDelay -> "worry" // 遅延=困り顔
else -> "normal"
}
val resId = resources.getIdentifier("${charId}_${expression}", "drawable", packageName)
imageView.setImageResource(resId)
}
private fun pickImportantRssItem(list: List<NewsItem>): NewsItem? {
if (list.isEmpty()) return null
val keywords = listOf("工事", "運休", "変更", "改良", "新設", "増発", "減便")
// ① 重要記事を抽出
val important = list.filter { item ->
keywords.any { kw ->
item.title.contains(kw) || item.body.contains(kw)
}
}
// ② 重要記事が複数 → ランダムに1件
if (important.isNotEmpty()) {
return important.random()
}
// ③ 重要記事ゼロ → 全RSSからランダム
return list.random()
}
private fun generateAutoCommentFromInfo(text: String) {
Thread {
try {
val charId = CharacterSettings.loadCharacter(this)
val infoType = InfoClassifier.classify(text)
val json = CharacterReplyManager().replyWithAI(
characterId = charId,
info = text,
type = infoType
)
val clean = AiReplyParser.extractTextFromResponse(json)
runOnUiThread {
findViewById<TextView>(R.id.textAutoComment).text = clean
}
} catch (e: Exception) { e.printStackTrace() }
}.start()
}
private fun setInitialCharacterImage() {
val charId = CharacterSettings.loadCharacter(this)
val imageView = findViewById<ImageView>(R.id.imageCharacter)
val drawable = resources.getIdentifier("${charId}_smile", "drawable", packageName)
imageView.setImageResource(drawable)
}
private fun applyCharacterLayoutDefault() {
val img = findViewById<ImageView>(R.id.imageCharacter)
val scale = resources.displayMetrics.density
val lp = img.layoutParams as ViewGroup.MarginLayoutParams
lp.marginStart = (-8 * scale).toInt()
lp.topMargin = (-18 * scale).toInt()
img.layoutParams = lp
}
private fun pickRandomRssItem(list: List<NewsItem>): NewsItem? {
if (list.isEmpty()) return null
return list.random() // ← ランダム取得
}
}
// =========================================================
// コメント生成
// =========================================================

View File

@@ -20,4 +20,7 @@ class MarisaResponder : CharacterResponder {
"ふむ?何か気になることがあるんだな。他にも情報があれば教えてくれよ。"
}
}
fun reply(text: String): String = respond(text, InfoType.NORMAL)
}

View File

@@ -20,4 +20,6 @@ class MomokaResponder : CharacterResponder{
"気になることがあったんだね。よかったら、もう少し教えてねっ!"
}
}
fun reply(text: String): String = respond(text, InfoType.NORMAL)
}

View File

@@ -0,0 +1,7 @@
package com.example.curation_train_app
data class RailLine(
val id: String, // "hanwa"
val name: String, // "阪和線"
val company: String // "jr_west"
)

View File

@@ -0,0 +1,17 @@
package com.example.curation_train_app
object RailLineData {
val lines = listOf(
RailLine("hanwa", "阪和線", "jr_west"),
RailLine("kyoto", "JR京都線", "jr_west"),
RailLine("kobes", "JR神戸線", "jr_west"),
RailLine("takarazuka", "JR宝塚線", "jr_west"),
// 南海
RailLine("nankai_main", "南海本線", "nankai"),
RailLine("koya", "高野線", "nankai"),
RailLine("senboku", "泉北高速線", "senboku"),
// ここにどんどん追加できる
)
}

View File

@@ -20,4 +20,8 @@ class ReimuResponder : CharacterResponder {
"気になったのね。内容をもう少し教えてくれれば、ちゃんと見てみるわよ。"
}
}
fun reply(text: String): String = respond(text, InfoType.NORMAL)
}

View File

@@ -20,4 +20,7 @@ class SanaeResponder : CharacterResponder {
"気になる点がありましたか?よければ詳しく教えてくださいね。"
}
}
fun reply(text: String): String = respond(text, InfoType.NORMAL)
}

View File

@@ -20,4 +20,7 @@ class SayakResponder : CharacterResponder {
"気になる点があるのね。もう少し詳しく教えてくれれば、整理してお話しできると思うわ。"
}
}
fun reply(text: String): String = respond(text, InfoType.NORMAL)
}

View File

@@ -20,4 +20,6 @@ class flandreResponder : CharacterResponder {
"なになに?気になることがあるの?フランに教えてほしいのだ!"
}
}
fun reply(text: String): String = respond(text, InfoType.NORMAL)
}

View File

@@ -0,0 +1,10 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#FFFFFF"/>
<corners android:radius="12dp"/>
<padding
android:left="8dp"
android:top="8dp"
android:right="8dp"
android:bottom="8dp"/>
<stroke android:width="1dp" android:color="#DDDDDD"/>
</shape>

View File

@@ -97,4 +97,44 @@
</LinearLayout>
<LinearLayout
android:id="@+id/layoutNotify"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="お知らせ設定"
android:textStyle="bold"
android:padding="4dp"/>
<CheckBox
android:id="@+id/checkboxDelay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="運行・遅延情報"/>
<CheckBox
android:id="@+id/checkboxOuting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="お出かけ関連情報"/>
<CheckBox
android:id="@+id/checkboxCampaign"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="キャンペーン・お楽しみ情報"/>
<CheckBox
android:id="@+id/checkboxOther"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="その他"/>
</LinearLayout>
</ScrollView>

View File

@@ -129,77 +129,42 @@
android:padding="8dp" />
<!-- ▼▼ キャラ応答テストUI ▼▼ -->
<LinearLayout
android:id="@+id/testArea"
android:id="@+id/commentArea"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp"
android:background="#EEEEEE">
android:orientation="horizontal"
android:padding="8dp">
<EditText
android:id="@+id/inputText"
android:layout_width="match_parent"
<!-- 吹き出し(白枠) -->
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:hint="テスト用の文章を入力" />
<EditText
android:id="@+id/inputCharacter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="キャラIDreimu / akane" />
<Button
android:id="@+id/btnTestReply"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="キャラにしゃべらせる" />
<TextView
android:id="@+id/textTestResult"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="※結果がここに表示されます"
android:paddingTop="8dp" />
<!-- ▼▼ キャラ表示エリア(正しい構造) ▼▼ -->
<FrameLayout
android:id="@+id/bottomArea"
android:layout_width="match_parent"
android:layout_height="180dp"
android:background="@drawable/bg_comment_bubble"
android:padding="12dp">
<!-- 左側のセリフ枠 -->
<TextView
android:id="@+id/textCharacterComment"
android:layout_width="250dp"
android:id="@+id/textAutoComment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/comment_background_white"
android:padding="13dp"
android:text="ちょっとした一言"
android:textSize="13sp"
android:layout_gravity="center_vertical|start"
android:translationX="0dp"
android:translationY="0dp" />
android:textSize="15sp" />
</LinearLayout>
<!-- 右下のキャラ画像 -->
<!-- キャラ画像(右寄せ) -->
<ImageView
android:id="@+id/imageCharacter"
android:layout_width="200dp"
android:layout_height="250dp"
android:adjustViewBounds="false"
android:scaleType="fitCenter"
android:src="@drawable/akane_01_icon"
android:layout_gravity="bottom|end"
android:layout_margin="8dp"
android:cropToPadding="false"
/>
android:layout_width="110dp"
android:layout_height="110dp"
android:layout_marginStart="6dp"
android:layout_gravity="bottom" />
</LinearLayout>
</FrameLayout>
<!-- ▼▼ キャラ応答テストUI ▼▼ -->
<!-- ▲▲ キャラ表示エリア ▲▲ -->
</LinearLayout>
<!-- ▲▲ キャラ応答テストUI ▲▲ -->
<!-- ▲▲ キャラ応答テストUI ▲▲ -->

View File

@@ -119,56 +119,56 @@
android:id="@+id/btnReimu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="🔴 霊夢(キャラ紹介"
android:text="🔴 霊夢(落ち着いた案内役"
android:layout_marginTop="4dp"/>
<Button
android:id="@+id/btnMarisa"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="🟡 魔理沙(キャラ紹介"
android:text="🟡 魔理沙(専門的な深掘り担当"
android:layout_marginTop="4dp"/>
<Button
android:id="@+id/btnFrandle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="🟠 フラン(キャラ紹介"
android:text="🟠 フラン(明るい元気ムードメーカー"
android:layout_marginTop="4dp"/>
<Button
android:id="@+id/btnSanae"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="🟢 早苗(キャラ紹介"
android:text="🟢 早苗(専門的な深掘り担当"
android:layout_marginTop="4dp"/>
<Button
android:id="@+id/btnAkane"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="🟨 あかね(キャラ紹介"
android:text="🟨 あかね(素直なリアクション枠"
android:layout_marginTop="4dp"/>
<Button
android:id="@+id/btnMomoka"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="🍑 ももか(キャラ紹介"
android:text="🍑 ももか(素直なリアクション枠"
android:layout_marginTop="4dp"/>
<Button
android:id="@+id/btnSayaka"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="🟦 さやか(キャラ紹介"
android:text="🟦 さやか(穏やかな整理・補足役"
android:layout_marginTop="4dp"/>
<Button
android:id="@+id/btnHiyori"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="🟧 ひより(キャラ紹介"
android:text="🟧 ひより(柔らかい反応の癒し枠"
android:layout_marginTop="4dp"/>
</LinearLayout>