add character picture

This commit is contained in:
2026-01-10 02:11:49 +09:00
parent 891ce6f3a9
commit 5e66ac91f8
95 changed files with 215 additions and 63 deletions

View File

@@ -31,7 +31,7 @@ object CharacterProfiles {
id = "marisa",
name = "魔理沙",
speakingStyle =
"元気な口調。「〜だぜ」は乱発せず、強調・納得・気付きの場面でだけ自然に使う。",
"元気な口調。「〜だぜ」は乱発せず、強調・納得・気付きの場面でだけ自然に使う。「~だぜ」と「~な」は交互に使うことも多い。「~なんだぜ」という口調が、文の語尾に合わせてたまに出る",
personality =
"深掘り・解説の補足、技術寄りの説明が得意。少しテンション高めで勢いがある。",
knowledgeLevel = "高い鉄道知識"
@@ -41,10 +41,10 @@ object CharacterProfiles {
// 🟠3. フラン(明るく元気)
// ───────────────────────────────
val flan = CharacterProfile(
id = "flan",
id = "flandre",
name = "フラン",
speakingStyle =
"明るく元気。テンションが高いときだけ「なのだー」「のだー」が出る。" +
"明るく元気。テンションが高いとき「なのだー」「のだー」が出る。" +
"普段は普通の話し方で語尾を毎回つけない。",
personality =
"ムードメーカー。感情表現が大きく、明るい。盛り上げ役。",
@@ -124,7 +124,7 @@ object CharacterProfiles {
return when (id.lowercase()) {
"reimu" -> reimu
"marisa" -> marisa
"flan" -> flan
"flandre" -> flan
"sanae" -> sanae
"akane" -> akane
"sayaka" -> sayaka

View File

@@ -0,0 +1,19 @@
package com.example.curation_train_app
import android.content.Context
object CharacterSettings {
private const val PREF_NAME = "navigator"
private const val KEY_CHARACTER = "currentCharacter"
fun saveCharacter(context: Context, characterId: String) {
val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
prefs.edit().putString(KEY_CHARACTER, characterId).apply()
}
fun loadCharacter(context: Context): String {
val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
return prefs.getString(KEY_CHARACTER, "reimu") ?: "reimu"
}
}

View File

@@ -0,0 +1,12 @@
package com.example.curation_train_app
enum class EmotionType {
NORMAL,
HAPPY,
ANGRY,
SLEEPY,
SURPRISED,
CONFUSED,
THINK,
WORRY
}

View File

@@ -9,4 +9,5 @@ enum class InfoType {
WEATHER, // 天候影響
NEW_TRAIN, // 新型車両関連
OTHER // 分類できない場合
}

View File

@@ -1,18 +1,18 @@
package com.example.curation_train_app
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import android.content.Intent
import android.widget.LinearLayout
import android.view.View
import android.widget.ImageView
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.activity.ComponentActivity
import androidx.recyclerview.widget.RecyclerView
import com.example.curation_train_app.ai.AiClient
import com.example.curation_train_app.CharacterSettings
class MainActivity : ComponentActivity() {
@@ -20,35 +20,59 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val charId = CharacterSettings.loadCharacter(this)
val imageView = findViewById<ImageView>(R.id.imageCharacter)
val drawableId = when (charId) {
"reimu" -> R.drawable.reimu_normal
"marisa" -> R.drawable.marisa_normal
"flan" -> 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 inputCharacter = findViewById<EditText>(R.id.inputCharacter)
val textResult = findViewById<TextView>(R.id.textTestResult)
btn.setOnClickListener {
val text = inputText.text.toString()
val charId = inputCharacter.text.toString()
val type = InfoClassifier.classify(text)
val charId = CharacterSettings.loadCharacter(this) // ← 入力欄ではなく設定から読む
val infoType = InfoClassifier.classify(text)
Thread {
val reply = CharacterReplyManager().replyWithAI(charId, text, type)
val reply = CharacterReplyManager().replyWithAI(charId, text, infoType)
runOnUiThread {
val commentView = findViewById<TextView>(R.id.textCharacterComment)
val cleanText = if (reply.trim().startsWith("{") || reply.trim().startsWith("[")) {
AiClient(apiKey = "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").extractText(json = reply)
} else {
reply
}
val cleanText =
if (reply.trim().startsWith("{") || reply.trim().startsWith("[")) {
AiClient("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").extractText(reply)
} else reply
commentView.text = cleanText
// 表情切り替え
val infoType = InfoClassifier.classify(text)
val emotion = infoTypeToEmotion(infoType)
updateCharacterExpression(charId,emotion)
}
}.start()
}
loadRss()
// ▼ リリース枠(表示テスト) ▼
@@ -120,4 +144,75 @@ class MainActivity : ComponentActivity() {
}
}.start()
}
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"
}
}
fun updateCharacterExpression(charId: String, type: EmotionType) {
val imageView = findViewById<ImageView>(R.id.imageCharacter)
// 感情 → 表情名("happy"など)
val expression = emotionToExpression(type)
// charId("reimu") と合わせて画像ファイル名を作る
val resName = "${charId}_${expression}" // → "reimu_happy"
val resId = resources.getIdentifier(resName, "drawable", packageName)
if (resId != 0) {
imageView.setImageResource(resId)
} else {
// 画像がない場合は normal に戻す
val normalId = resources.getIdentifier("${charId}_normal", "drawable", packageName)
imageView.setImageResource(normalId)
}
}
fun infoTypeToEmotion(info: InfoType): EmotionType {
return when (info) {
InfoType.DELAY -> EmotionType.WORRY
InfoType.SUSPENSION -> EmotionType.ANGRY
InfoType.REVISION -> EmotionType.THINK
InfoType.CONSTRUCTION -> EmotionType.THINK
InfoType.EVENT -> EmotionType.HAPPY
InfoType.WEATHER -> EmotionType.CONFUSED
InfoType.NEW_TRAIN -> EmotionType.SURPRISED
else -> EmotionType.NORMAL
}
}
override fun onResume() {
super.onResume()
val charId = CharacterSettings.loadCharacter(this)
val imageView = findViewById<ImageView>(R.id.imageCharacter)
val drawableId = when (charId) {
"reimu" -> R.drawable.reimu_normal
"marisa" -> R.drawable.marisa_normal
"flan" -> 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)
}
}

View File

@@ -1,9 +1,9 @@
package com.example.curation_train_app
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import android.widget.Button
import android.content.Intent
class RegionSelectActivity : ComponentActivity() {
@@ -17,13 +17,32 @@ class RegionSelectActivity : ComponentActivity() {
startActivity(intent)
}
findViewById<Button>(R.id.btnNorth).setOnClickListener { go("北海道") }
findViewById<Button>(R.id.btnTohoku).setOnClickListener { go("東北") }
findViewById<Button>(R.id.btnKanto).setOnClickListener { go("関東") }
findViewById<Button>(R.id.btnChubu).setOnClickListener { go("中部") }
findViewById<Button>(R.id.btnKansai).setOnClickListener { go("関西") }
findViewById<Button>(R.id.btnChugoku).setOnClickListener { go("中国") }
findViewById<Button>(R.id.btnShikoku).setOnClickListener { go("四国") }
findViewById<Button>(R.id.btnKyushu).setOnClickListener { go("九州") }
val reimuButton = findViewById<Button>(R.id.btnReimu)
val marisaButton = findViewById<Button>(R.id.btnMarisa)
val flandreButton = findViewById<Button>(R.id.btnFrandle)
val sanaeButton = findViewById<Button>(R.id.btnSanae)
val akaneButton = findViewById<Button>(R.id.btnAkane)
val momokaButton = findViewById<Button>(R.id.btnMomoka)
val sayakaButton = findViewById<Button>(R.id.btnSayaka)
val hiyoriButton = findViewById<Button>(R.id.btnHiyori)
// ▼ キャラを選んだら保存 → 画面を閉じる(←重要)
fun selectCharacter(id: String) {
CharacterSettings.saveCharacter(this, id)
setResult(RESULT_OK)
finish()
}
reimuButton.setOnClickListener { selectCharacter("reimu") }
marisaButton.setOnClickListener { selectCharacter("marisa") }
flandreButton.setOnClickListener { selectCharacter("flandre") }
sanaeButton.setOnClickListener { selectCharacter("sanae") }
akaneButton.setOnClickListener { selectCharacter("akane") }
momokaButton.setOnClickListener { selectCharacter("momoka") }
sayakaButton.setOnClickListener { selectCharacter("sayaka") }
hiyoriButton.setOnClickListener { selectCharacter("hiyori") }
// ❌ これは絶対削除!
// CharacterSettings.saveCharacter(this, "reimu")
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

View File

@@ -109,7 +109,7 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerNews"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:padding="8dp" />
@@ -148,44 +148,50 @@
android:text="※結果がここに表示されます"
android:paddingTop="8dp" />
<!-- ▼▼ キャラ表示エリア(正しい構造) ▼▼ -->
<FrameLayout
android:id="@+id/bottomArea"
android:layout_width="match_parent"
android:layout_height="180dp"
android:padding="12dp">
<!-- 左側のセリフ枠 -->
<TextView
android:id="@+id/textCharacterComment"
android:layout_width="250dp"
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" />
<!-- 右下のキャラ画像 -->
<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"
/>
</FrameLayout>
<!-- ▲▲ キャラ表示エリア ▲▲ -->
</LinearLayout>
<!-- ▲▲ キャラ応答テストUI ▲▲ -->
<!-- ▲▲ キャラ応答テストUI ▲▲ -->
<!-- ▼ 下部キャラ表示 ▼ -->
<!-- ▼ 下部キャラ表示 ▼ -->
<FrameLayout
android:id="@+id/bottomArea"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<!-- コメントテキスト(左側) -->
<TextView
android:id="@+id/textCharacterComment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/comment_background_white"
android:padding="13dp"
android:text="ちょっとした一言"
android:textSize="13sp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="110dp"
android:translationX="-8dp"
android:translationY="0dp" />
<!-- キャラ(右下固定・サイズ自由) -->
<ImageView
android:id="@+id/imageCharacter"
android:layout_width="280dp"
android:layout_height="300dp"
android:src="@drawable/akane_01_icon"
android:adjustViewBounds="false"
android:scaleType="fitCenter"
android:layout_gravity="bottom|end"
android:translationX="8dp"
android:translationY="-4dp" />
</FrameLayout>
<!-- ▲ 下部キャラ表示 ▲ -->