大和.の書き溜め

苦手科目が国語なので察してください。

スクフェス2用LP管理アプリ作成記録

個人的な進捗状況確認も含め、記録として書いていきます。

スクフェス2配信日:4/15

15日:①決意

無印スクフェスでは6分で1LP回復だが、今作では5分で1LP回復に変更。今までの管理アプリが使えない。なので勉強がてら、Androidでの管理アプリ製作にチャレンジすることを決意。

 

 

Android Studioインストール

Googleさん推奨(?)のプログラミング言語名は”Kotlin”。この言語はプログラミングの勉強を始める一歩目の言語としても良いと思う。下手にCとかで満足しちゃうと、オブジェクト指向とかの概念が理解しづらくなりそう。というか自分が現になってる。昔勉強したけど、今では完全に忘却なさっています。

developer.android.com

開発言語としてJava等も使えるので、そっちが理解出来てる人はそっちでOK。というか"Kotlin”自体がJavaベースなので、素早く習得できるかも。

 

③公式チュートリアル

developer.android.com

結構楽しみながらやってた。

 

このときの考えとしては、チュートリアル内でのコードを上手く使って実装できないかな~とか考えていた。これが誤りであるとは、未だ気付いていなかった。

 

 

16日:チュートリアルの続き。

 

 

 

 

4/17:概形完成。

 

 

 

 

 

Bottunを使ってBoolean型変数を弄っていく方針で考えるが、上手くいかない。

 

 

4/18:ぶっちゃけ25時頃まで一切進まなかった。進捗ダメです。ってやつ。深夜にサイコロゲームのチュートリアルでのonClick内の処理を思い出し、試す。その結果、クソ仕様に気づく。

これのせいで1日以上が無駄になった。今でもこの仕様には「?」が菜々個付く。

 

 

4/19:スクスタのガチャ動画作成のため、進展無し。

www.youtube.com

暇があるなら見てください。暇じゃないなら早いめにタスクを終わらせてから見てください。

 

4/20:タイマー機能の実装がガチで分からなくて詰んでる。Android Studioでプロジェクトを新しく作るときに"Phone and Tablet"→"Empty Activity"を選んでるんだけど、そこでコード内に出てくるSetContent{ (アプリの名前)Thehe {Surface(){~~

って書き方をしてる先駆者が居なくて分からん。Javaの書き方が分かってれば読み換えて理解できるんだろうけど、数年前の授業でやったきりなので全く覚えてない...

とりあえず各行の、ふんわりとした意味合いを分析する。

クソコードを一旦公開。通知機能を作ろうとして断念した痕跡があります。

package com.example.lpcheck

import android.os.Bundle
import android.os.CountDownTimer
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.lpcheck.ui.theme.LPCheckTheme
import java.text.SimpleDateFormat
import java.util.*

class MainActivity : ComponentActivity() {

private val CHANNEL_ID = "LP_check"
private val notificationID = 101

//8)横断的に使うやつを用意
private lateinit var timer: CountDownTimer
private var remainingTime: Long = 0 //9)残り時間(後で代入するので一旦0
//private lateinit var mp:MediaPlayer //12) // タイマーのアラーム音

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {// 見た目を生成
LPCheckTheme {
Surface()
{
TipTimeScreen()// 関数化しすぎて理解しにくくなっている?汚くてもいいからMainに書くべきか?
}

}
//createNotificationChannel()
//sendNotification()
}
}

//private fun createNotificationChannel(){
//if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
//val name = "Notification Title"
//val descriptionText = "Notification Description"
//val importance = NotificationManager.IMPORTANCE_DEFAULT
//val channel = NotificationChannel(CHANNEL_ID,name,importance).apply {
//description = descriptionText
//}
//val notificationManager: NotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
//notificationManager.createNotificationChannel(channel)
// }
//}

//private fun sendNotification(){
// val intent = Intent(this, MainActivity::class.java).apply{
// flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
// }
//val pendingIntent: PendingIntent = PendingIntent.getActivity(this, 0, intent, 1)

// val builder = NotificationCompat.Builder(this, CHANNEL_ID)
// .setContentTitle("Example Title")
// .setContentText("Example Description")
// .setPriority(NotificationCompat.PRIORITY_DEFAULT)

// with(NotificationManagerCompat.from(this)){
// notify(notificationID, builder.build())
// }
// }
}

@Composable
fun TipTimeScreen(){
//var amountInput by remember { mutableStateOf("") }
var maxLPInput by remember { mutableStateOf("") }
var nowLPInput by remember { mutableStateOf("") }
var result by remember { mutableStateOf(1) }

//val amount = amountInput.toIntOrNull() ?: 0
var maxLP = maxLPInput.toIntOrNull() ?: 0
var nowLP = nowLPInput.toIntOrNull() ?: 0

var isEnabled : Boolean
by remember { mutableStateOf(true) }

val testText1 = "that's great!"
val testText2 = "Is that great?"
val testText3 = "created by an incompetent programmer"

isEnabled = when (result) {
0 -> false
else -> true
}
val restoreLPTime: Int = 300
var resultTime by remember {
mutableStateOf(0)
}
resultTime = (maxLP - nowLP) * restoreLPTime
if(maxLP < nowLP){ resultTime = 0}

var leftSecond by remember { mutableStateOf(0) }

var nowText by remember { mutableStateOf("") }

nowText = when (result) {
1 -> "not started"
//else -> getToday()
else -> resultTime.toString()
}


Column(
modifier = Modifier.padding(32.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
)
{
Text(
text = stringResource(R.string.max_lp),
fontSize = 24.sp,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
//Spacer(Modifier.height(16.dp))
EditMaxLPField(
isEnabled,
value = maxLPInput,
onValueChange = { maxLPInput = it }
)
//Spacer(Modifier.height(24.dp))
Text(
text = stringResource(R.string.now_lp),
fontSize = 24.sp,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
EditNowLPField(
isEnabled,
value = nowLPInput,
onValueChange = { nowLPInput = it }
)
Text(
text = stringResource(R.string.nessesary_time,
calculateLPDay(resultTime), calculateLPHour(resultTime),
calculateLPMinute(resultTime), calculateLPSecond(resultTime)
),
//text = "",
modifier = Modifier.align(Alignment.CenterHorizontally),
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
Row(
modifier = Modifier.padding(vertical = 0.dp),
verticalAlignment = Alignment.CenterVertically
) {
Button(onClick = { /*TODO*/ result = 0}) {
Text(
text = "START",
color = Color.White,
fontSize = 24.sp
)
}
Spacer(modifier = Modifier.padding(10.dp))

Button(onClick = { /*TODO*/ result = 1 }) {
Text(
text = "STOP",
color = Color.White,
fontSize = 24.sp
)
}
}
Text(
text = nowText,
fontSize = 17.sp,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
}
}

@Composable
fun getToday(): String {
val date = Date()
//val format = SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault())
val format = SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault())
return format.format(date)
}

@Composable
fun calculateLPDay(resultTime: Int): Int{
val secondToDay: Int = 86400
return resultTime / secondToDay
}

@Composable
fun calculateLPHour(resultTime: Int): Int{
val secondToHour:Int = 3600
val dayIs: Int = 86400
var resultHour = resultTime

while(resultHour >= dayIs){resultHour -= dayIs}

return resultHour / secondToHour
}

@Composable
fun calculateLPMinute(resultTime: Int): Int{
val secondToMinute:Int = 60
val hourIs: Int = 3600
var resultMinute = resultTime

while(resultMinute >= hourIs){resultMinute -= hourIs}

return resultMinute / secondToMinute
}

@Composable
fun calculateLPSecond(resultTime: Int): Int{
val minuteIs: Int = 60
var resultSecond = resultTime

while(resultSecond >= minuteIs){resultSecond -= minuteIs}

return resultSecond
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EditMaxLPField(
isEnabled: Boolean,
value: String,
onValueChange: (String) -> Unit
) {
TextField(
value = value,
onValueChange = onValueChange,
label = { Text(stringResource(R.string.max_lp_input_field)) },
enabled = isEnabled,
modifier = Modifier.fillMaxWidth(),
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EditNowLPField(
isEnabled: Boolean,
value: String,
onValueChange: (String) -> Unit
) {
TextField(
value = value,
onValueChange = onValueChange,
label = { Text(stringResource(R.string.now_lp_input_field)) },
enabled = isEnabled,
modifier = Modifier.fillMaxWidth(),
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
LPCheckTheme {
TipTimeScreen()
}
}

 

関数化しすぎて逆に処理しづらくなっている感がある。(KotlinとかJavaは関数って言わないか)とりあえず汚くてもいいから、Mainの部分に全部ぶち込む勢いで良いかもしれない。

20:30 Composeを使った書き方が、理解しようにも思ったような処理にならないし、似たような書き方してる人全然居なくね...?って思ったら、割と最近に出た書き方だと知る。これなら昔の教科書引っ張り出しながら、Javaで書いた方が良くね...って思った。Java先駆者達を見てみると、.javaファイルと.xmlファイルの2つを編集しているが、昔習ったときにそんなことしてたっけ...?と思い、教科書を見てみると、.javaファイルしか書いてないじゃないか...Android用になるとまた変わってくるのかもだけど、とりあえずJavaってみるか...となる。

 

21:30 Chat GPTに聞きながら適当に弄ってたら、それっぽく動いた...CountDownTimerの配置とか書き方が駄目だったっぽいた。まだ頭の中が整理出来てないけど、一安心。結局Javaらずに出来そう。

記録のために、一応コードドボン。コメント部分で消し忘れとかあるけど、そこは愛嬌ってことで。

package com.example.lpcheck

import android.os.Bundle
import android.os.CountDownTimer
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.lpcheck.ui.theme.LPCheckTheme
import java.text.SimpleDateFormat
import java.util.*

class MainActivity : ComponentActivity() {

private val CHANNEL_ID = "LP_check"
private val notificationID = 101

//8)横断的に使うやつを用意
private lateinit var timer: CountDownTimer
private var remainingTime: Long = 0 //9)残り時間(後で代入するので一旦0

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {// 見た目を生成。するはずだった。ほぼ全部ぶち込め。
LPCheckTheme {
Surface()
{
TipTimeScreen()// 関数化しすぎて理解しにくくなっている?汚くてもいいからMainに書くべきか?
}
}
//createNotificationChannel()
//sendNotification()
}
}

//private fun createNotificationChannel(){
//if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
//val name = "Notification Title"
//val descriptionText = "Notification Description"
//val importance = NotificationManager.IMPORTANCE_DEFAULT
//val channel = NotificationChannel(CHANNEL_ID,name,importance).apply {
//description = descriptionText
//}
//val notificationManager: NotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
//notificationManager.createNotificationChannel(channel)
// }
//}

//private fun sendNotification(){
// val intent = Intent(this, MainActivity::class.java).apply{
// flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
// }
//val pendingIntent: PendingIntent = PendingIntent.getActivity(this, 0, intent, 1)

// val builder = NotificationCompat.Builder(this, CHANNEL_ID)
// .setContentTitle("Example Title")
// .setContentText("Example Description")
// .setPriority(NotificationCompat.PRIORITY_DEFAULT)

// with(NotificationManagerCompat.from(this)){
// notify(notificationID, builder.build())
// }
// }
}

@Composable
fun TipTimeScreen(){
// 見た目を生成。するはずだった。ほぼ全部ぶち込め。
//var amountInput by remember { mutableStateOf("") }
var maxLPInput by remember { mutableStateOf("") }
var nowLPInput by remember { mutableStateOf("") }

//val amount = amountInput.toIntOrNull() ?: 0
var maxLP = maxLPInput.toIntOrNull() ?: 0
var nowLP = nowLPInput.toIntOrNull() ?: 0

//入力欄が使えるかどうか trueなら入力可能
var isEnabled: Boolean
by remember { mutableStateOf(true) }

val testText1 = "that's great!"
val testText2 = "Is that great?"
val testText3 = "created by an incompetent programmer"

var preText by remember {
mutableStateOf("")
}

var nowText by remember {
mutableStateOf("not started")
}
var leftTime by remember {
mutableStateOf(0)
}
val restoreLPTime: Int = 300
var resultTime by remember {
mutableStateOf(0)
}
resultTime = (maxLP - nowLP) * restoreLPTime
if (maxLP < nowLP) {
resultTime = 0
}

var remaindTime by remember {
mutableStateOf(0L)
}
//var startTime = resultTime.toLong()
remaindTime = resultTime * 1000L

if(!isEnabled) {
resultTime -= leftTime
}
preText = getToday()

Column(
modifier = Modifier.padding(32.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
)
{
Text(
text = stringResource(R.string.max_lp),
fontSize = 24.sp,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
//Spacer(Modifier.height(16.dp))
EditMaxLPField(
isEnabled,
value = maxLPInput,
onValueChange = { maxLPInput = it }
)
//Spacer(Modifier.height(24.dp))
Text(
text = stringResource(R.string.now_lp),
fontSize = 24.sp,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
EditNowLPField(
isEnabled,
value = nowLPInput,
onValueChange = { nowLPInput = it }
)
Text(
text = stringResource(
R.string.nessesary_time,
calculateLPDay(resultTime),
calculateLPHour(resultTime),
calculateLPMinute(resultTime),
calculateLPSecond(resultTime)
),
//text = "",
modifier = Modifier.align(Alignment.CenterHorizontally),
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
Row(
modifier = Modifier.padding(vertical = 0.dp),
verticalAlignment = Alignment.CenterVertically
)
{
Button(onClick = {
/*TODO*/
isEnabled = false
var timer = object : CountDownTimer(remaindTime, 1000) {
//[4-1]途中経過・残り時間
override fun onTick(p0: Long) {
//TODO("Not yet implemented")
//残り時間を表示
leftTime++
nowText = "${p0 / 1000}" //秒単位
}

//[4-2]終了設定
override fun onFinish() {
//TODO("Not yet implemented")
nowText = testText3
remaindTime = resultTime * 1000L
leftTime = 0
}
}.start()
}) {
Text(
text = "START",
color = Color.White,
fontSize = 24.sp
)
}

Spacer(modifier = Modifier.padding(10.dp))

Button(onClick = {
/*TODO*/
isEnabled = true
}) {
Text(
text = "STOP",
color = Color.White,
fontSize = 24.sp
)
//timer.cancel()
}
}
Text(
text = nowText,
fontSize = 17.sp,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Text(
text = preText,
fontSize = 17.sp,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
}
}

@Composable
fun getToday(): String {
val date = Date()
//val format = SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault())
val format = SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault())
return format.format(date)
}

@Composable
fun calculateLPDay(resultTime: Int): Int{
val secondToDay: Int = 86400
return resultTime / secondToDay
}

@Composable
fun calculateLPHour(resultTime: Int): Int{
val secondToHour:Int = 3600
val dayIs: Int = 86400
var resultHour = resultTime

while(resultHour >= dayIs){resultHour -= dayIs}

return resultHour / secondToHour
}

@Composable
fun calculateLPMinute(resultTime: Int): Int{
val secondToMinute:Int = 60
val hourIs: Int = 3600
var resultMinute = resultTime

while(resultMinute >= hourIs){resultMinute -= hourIs}

return resultMinute / secondToMinute
}

@Composable
fun calculateLPSecond(resultTime: Int): Int{
val minuteIs: Int = 60
var resultSecond = resultTime

while(resultSecond >= minuteIs){resultSecond -= minuteIs}

return resultSecond
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EditMaxLPField(
isEnabled: Boolean,
value: String,
onValueChange: (String) -> Unit
) {
TextField(
value = value,
onValueChange = onValueChange,
label = { Text(stringResource(R.string.max_lp_input_field)) },
enabled = isEnabled,
modifier = Modifier.fillMaxWidth(),
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EditNowLPField(
isEnabled: Boolean,
value: String,
onValueChange: (String) -> Unit
) {
TextField(
value = value,
onValueChange = onValueChange,
label = { Text(stringResource(R.string.now_lp_input_field)) },
enabled = isEnabled,
modifier = Modifier.fillMaxWidth(),
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
LPCheckTheme {
TipTimeScreen()
}
}

現状だとSTARTボタン部分に引っ付いてる状態だから、STOPボタン側にコード書いたとしても止められないって感じ。専門用語を使うと、インスタンスが別ダメってこと。”インスタンス”ね、”インスタント”じゃないよ。

調べてみるとCountDownTimerは誤差が気になる、という意見が。ここは上手い具合に回避策を見つけたい。現段階だと、スマホの時計時間を取得して...とか考えてるけど、実装は遠そうだ...

ちなみに現状だとSTARTを押すとタイマーが動いたまま止まらない。この辺も要改善。

 

日付変更直前:タイマーが停止できるように。変えた部分と、その前後を抜粋。

var remaindTime by remember {
mutableStateOf(0L)
}

//var startTime = resultTime.toLong()
remaindTime = resultTime * 1000L

var timer = object : CountDownTimer(remaindTime, 1000) {
//[4-1]途中経過・残り時間
override fun onTick(p0: Long) {
//TODO("Not yet implemented")
//残り時間を表示
leftTime++
nowText = "${p0 / 1000}" //秒単位
if(remaindTime <= 0L){}
}

//[4-2]終了設定
override fun onFinish() {
//TODO("Not yet implemented")
nowText = testText3
remaindTime = resultTime * 1000L
leftTime = 0
}
}

if(!isEnabled) {
resultTime -= leftTime
}
preText = getToday()

Column(
modifier = Modifier.padding(32.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
)
{
Text(
text = stringResource(R.string.max_lp),

var timer = object : CountDownTimer(~~~の部分を外に追いやった。他の変数と同じような扱いに変更。

Row(
modifier = Modifier.padding(vertical = 0.dp),
verticalAlignment = Alignment.CenterVertically
)
{
Button(onClick = {
/*TODO*/
isEnabled = false
timer.start()
}) {
Text(
text = "START",
color = Color.White,
fontSize = 24.sp
)
}

Spacer(modifier = Modifier.padding(10.dp))

Button(onClick = {
/*TODO*/
isEnabled = true
timer.cancel()
}) {
Text(
text = "STOP",
color = Color.White,
fontSize = 24.sp
)
}
}

STARTボタンに引っ付いているわけではないので、STOPボタンの部分でtimer.cancel()すれば止まる。しかし実際は裏で動いているっぽい?

分かりにくいと思うけど

LP上限ー回復済みLP×5分(300秒)でタイマーをセットし、STARTでタイマースタート、STOPでタイマーストップという流れ。画像の"295416"っていう数字は、プログラム上で2個のLP情報(Int型)からタイマーの時間をCountDownTimerに代入する際に、Long型(あと1LP=5分として処理してるので、一旦計算しやすいように秒(300秒)への変換も)へ変換する際の、Long型数値を可視化したもの。その下は半分遊びで現在時刻表示。

タイマー”START→STOP”のルーチン1回目は正常そうな動き(全回復まで~とLong型の"295416"と現在時刻が1秒ごとに更新される)をするけど、2回目以降は現在時刻以外は1秒にルーチンの実行回数分更新されるような振舞いをする。Long型の数値は表示がちらつく(一瞬別の数値が表示される。元々カウントしていた数値かな)、全回復まで~の部分は1秒にルーチン回数分秒数が減っていく。恐らくCountDownTimerがルーチン回数個生成されてしまうためだと考えられるが、”STOP”を押した際に、どうやって元々の”CountDownTimer”を消去(初期化?ブレイク?デストロイ?)するのかが分からない。。また詰まりそうで冷や冷やしています。パソコンずっと稼働してるから部屋は暑いけど。

 

一旦通知機能部分を消したコードをポイ。こんなことしてるせいで既に2万字を超えている。

package com.example.lpcheck

import android.os.Bundle
import android.os.CountDownTimer
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.lpcheck.ui.theme.LPCheckTheme
import java.text.SimpleDateFormat
import java.util.*

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {// 見た目を生成。
LPCheckTheme {
Surface()
{
TipTimeScreen()// 誰か助けて
}
}

}
}
}

@Composable
fun TipTimeScreen(){
var maxLPInput by remember { mutableStateOf("") }
var nowLPInput by remember { mutableStateOf("") }

var maxLP = maxLPInput.toIntOrNull() ?: 0
var nowLP = nowLPInput.toIntOrNull() ?: 0

//入力欄が使えるかどうか trueなら入力可能
var isEnabled: Boolean
by remember { mutableStateOf(true) }

val testText1 = "that's great!"
val testText2 = "Is that great?"
val testText3 = "created by an incompetent programmer"

var preText by remember {
mutableStateOf("")
}

var nowText by remember {
mutableStateOf("not started")
}
var leftTime by remember {
mutableStateOf(0)
}
val restoreLPTime: Int = 300
var resultTime by remember {
mutableStateOf(0)
}
resultTime = (maxLP - nowLP) * restoreLPTime
if (maxLP < nowLP) {
resultTime = 0
}

var remaindTime by remember {
mutableStateOf(0L)
}

//var startTime = resultTime.toLong()
remaindTime = resultTime * 1000L

var timer = object : CountDownTimer(remaindTime, 1000) {
//[4-1]途中経過・残り時間
override fun onTick(p0: Long) {
//TODO("Not yet implemented")
//残り時間を表示
leftTime++
nowText = "${p0 / 1000}" //秒単位
if(remaindTime <= 0L){}
}

//[4-2]終了設定
override fun onFinish() {
//TODO("Not yet implemented")
nowText = testText3
remaindTime = resultTime * 1000L
leftTime = 0
}
}

if(!isEnabled) {
resultTime -= leftTime
}
preText = getToday()

Column(
modifier = Modifier.padding(32.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
)
{
Text(
text = stringResource(R.string.max_lp),
fontSize = 24.sp,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
//Spacer(Modifier.height(16.dp))
EditMaxLPField(
isEnabled,
value = maxLPInput,
onValueChange = { maxLPInput = it }
)
//Spacer(Modifier.height(24.dp))
Text(
text = stringResource(R.string.now_lp),
fontSize = 24.sp,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
EditNowLPField(
isEnabled,
value = nowLPInput,
onValueChange = { nowLPInput = it }
)
Text(
text = stringResource(
R.string.nessesary_time,
calculateLPDay(resultTime),
calculateLPHour(resultTime),
calculateLPMinute(resultTime),
calculateLPSecond(resultTime)
),
modifier = Modifier.align(Alignment.CenterHorizontally),
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
Row(
modifier = Modifier.padding(vertical = 0.dp),
verticalAlignment = Alignment.CenterVertically
)
{
Button(onClick = {
/*TODO*/
isEnabled = false
timer.start()
}) {
Text(
text = "START",
color = Color.White,
fontSize = 24.sp
)
}

Spacer(modifier = Modifier.padding(10.dp))

Button(onClick = {
/*TODO*/
isEnabled = true
timer.cancel()
}) {
Text(
text = "STOP",
color = Color.White,
fontSize = 24.sp
)
}
}
Text(
text = nowText,
fontSize = 17.sp,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Text(
text = preText,
fontSize = 17.sp,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
}
}

@Composable
fun getToday(): String {
val date = Date()
//val format = SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault())
val format = SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault())
return format.format(date)
}

@Composable
fun calculateLPDay(resultTime: Int): Int{
val secondToDay: Int = 86400
return resultTime / secondToDay
}

@Composable
fun calculateLPHour(resultTime: Int): Int{
val secondToHour:Int = 3600
val dayIs: Int = 86400
var resultHour = resultTime

while(resultHour >= dayIs){resultHour -= dayIs}

return resultHour / secondToHour
}

@Composable
fun calculateLPMinute(resultTime: Int): Int{
val secondToMinute:Int = 60
val hourIs: Int = 3600
var resultMinute = resultTime

while(resultMinute >= hourIs){resultMinute -= hourIs}

return resultMinute / secondToMinute
}

@Composable
fun calculateLPSecond(resultTime: Int): Int{
val minuteIs: Int = 60
var resultSecond = resultTime

while(resultSecond >= minuteIs){resultSecond -= minuteIs}

return resultSecond
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EditMaxLPField(
isEnabled: Boolean,
value: String,
onValueChange: (String) -> Unit
) {
TextField(
value = value,
onValueChange = onValueChange,
label = { Text(stringResource(R.string.max_lp_input_field)) },
enabled = isEnabled,
modifier = Modifier.fillMaxWidth(),
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EditNowLPField(
isEnabled: Boolean,
value: String,
onValueChange: (String) -> Unit
) {
TextField(
value = value,
onValueChange = onValueChange,
label = { Text(stringResource(R.string.now_lp_input_field)) },
enabled = isEnabled,
modifier = Modifier.fillMaxWidth(),
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
LPCheckTheme {
TipTimeScreen()
}
}

最近寝るのが遅くなってて焦る。。

 

4/21:用事で遠出していたので進展無し。行き5時間、帰り7時間で合計12時間運転していたことが判明。さすがに疲れたので頭が回らない。

 

4/22:スマブラが楽しい。カズヤで遊んでた。

 

4/23&24:イベントアイテムを使って無限にライブ出来てしまうことに気付いてしまう。どうやらランク100までランクアップに必要な経験値量が変わらないらしい。これいつ終わるん...?って思いながらひたすら画面を叩く。

右下のイベントアイテム個数が変わらないどころか逆に増えていく現象。こんなんで大丈夫なのか...?とゲームに対して不安を抱き始める。これのせいで製作の手が止まるっていうのもあるし、純粋にアプリ作る気力も無くなっていってる...正直マズい。また色々ブログで愚痴るかもしれない。。

少なくともこの日のランクはこれぐらい。

 

4/25&26:

 

さすがに落ち着いたので、明日からは再開できそう。

 

最終的にランクはここまで上がりました。譜面叩きすぎてスマホの寿命が確実に縮んでる。

初回クリアだと報酬で石60個が貰えるので、それを使ってリリースガチャで天井をしてみることに。勧誘ポイントも勿体無いからね。

無事到達し、悩んだ挙句、愛さんをチョイス。スマイル属性要員が欲しかったのと覚醒前後のイラストで選びました。

A・ZU・NAの3人は自力で引けました。運が良すぎる。

 

4/27:30分考えたけど駄目だった。

 

4/28:19時頃。ggるのは一旦止めて、プログラムの動作を確認してみることに。それまではYouTubeで都市伝説系の動画を漁ってた。20日で発生していた問題点だが、どうやらタイマーが設定された時間で止まった場合は発生しないようだ。つまり、STOPボタンを押したときのみに発生しているということ。timer.cancel()を使っているが、これが原因か...?と思い始める。

 

22:55:onFinishを代わりに使えばいいのかと思えば、答えはNoだった。色々試しながら、諦めてggってみると、「onTick()の中にcancel()を入れると良い」的な事を言っているサイトを発見。

stackoverflow.com

条件分岐とか付け加えながらではあるが、それっぽい動きになった。

var timer: CountDownTimer? = null
timer = object : CountDownTimer(remaindTime, 1000) {
//[4-1]途中経過・残り時間
override fun onTick(p0: Long) {
//TODO("Not yet implemented")
//残り時間を表示
leftTime++
nowText = "${p0 / 1000}" //秒単位
if(isStopClicked){
this.cancel()
isStopClicked = false
//nowText = testText3
remaindTime = resultTime * 1000L
leftTime = 0
isEnabled = true
}
}

//[4-2]終了設定
override fun onFinish() {
//TODO("Not yet implemented")
nowText = testText3
remaindTime = resultTime * 1000L
leftTime = 0
isEnabled = true
}
}

とりあえずこれでOKってことで、次のステップに進むことに。現状ではバックグラウンド処理に対応していなかったり、通知機能が無かったりだが、どちらを先に手を付けようか...。勝手なイメージだが、通知機能を付けてしまえばバックグラウンド処理が勝手に出来そうな気がしている。気がしているだけだけど。

 

4/29:ダメでした。明日頑張ろう。そういえばイベントお疲れ様でした。無印スクフェス時代は終了1時間後に結果発表だったけど、今作では翌日発表みたいですね。まぁスクスタと同じだから、最近のゲームに合わせてるんですかね。

 

4/30:

マジか...

 

5/1:生活リズム改善のため早めに寝ることに。最近起きたときに異様なほど眠たくて、0時に寝ても10時とかにしか目覚めない。いったい何が起きているのか...

今日は調べものを軽くした程度。

developer.android.com

Notificationは通知という意味。軽くしか見ていないけど、Androidのバージョンんっぽいことを言っている部分があるので、スクフェス2の動作スペック情報を基にしようかな。

www.4gamer.net

ここを見ていると、スナドラ865以上、RAM4GB以上のAndroid 5.1以上が推奨環境のようだ。最低スペックは分からなかったけど、とりあえずOSバージョン情報さえ分かればOK。

5/2:AT乗った30分後にはMTに乗ってる、そんな日だった。

5/3:@Compose !!

developer.android.com

ここにチュートリアルとして”通知機能を実装する”っていうのがあれば超絶楽なんですけどね。。

5/4:最近の中弛み感半端ない。原因はLP漏れを基本しないぐらいに上限が増えてしまったことが挙げられる。現在のLP上限が256なんですけど、これだけ有ると100消費だけで終わらせて60とか中途半端にLPが残ってても、寝て起きても漏れてないんですよね。

つまるところ自分自身ですらアプリの必要性が薄れまくってるせいで弛んじゃってるということ。いやはやこれはマズいですよ。

5/5:仲間内でSBL。楽しかった。

5/6:

もう少しで交換できるんだ...

なんで覚醒後のイラスを変えた???


5/7:15:00:公式HPでサンプルコードのURLが貼ってあることがあるけど、たまにリンク切れ起こしてる現象。英語原文でページを開くと直ることもあるようだ。

19:00:Thx.

youtu.be

ようやく通知自体はチュートリアルベースだけど作成できた。

romannurik.github.io

必要なアイコンは上記のリンクからDL。適当にリネームしてインポート。

真ん中のボタンを押すと通知が生成される。

こんな感じ。とりあえずカウントダウンの経過秒数とかをリアルタイムで表示できるのかを試してみるか。

以下コード。先に通知を作成するコードを。てかクラス。名前は”MyNotification.kt”。

package com.example.notification_test

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat

class MyNotification(var context: Context, var title:String, var msg:String) {
val channelID:String = "NotificationTest."
val channelName:String = "NotificationTest..."
val notificationManager = context.applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
lateinit var notificationChannel: NotificationChannel
lateinit var notificationBilder:NotificationCompat.Builder
fun FirNotification() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationChannel =
NotificationChannel(channelID, channelName, NotificationManager.IMPORTANCE_HIGH)
notificationManager.createNotificationChannel(notificationChannel)
}

val intent = Intent(context, MainActivity::class.java)
val pendingIntent =
PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
notificationBilder = NotificationCompat.Builder(context, channelID)
notificationBilder.setSmallIcon(R.drawable.ic_stat_notifications)
notificationBilder.addAction(
R.drawable.ic_stat_notifications,
"Open Messageとか表示可能",
pendingIntent
)
notificationBilder.setContentTitle(title)
notificationBilder.setContentText(msg)
notificationBilder.setAutoCancel(true)
notificationManager.notify(100, notificationBilder.build())
}
}

次にメインのコード。てかこれもクラスだわな。名前は”MainActivity.kt”。

package com.example.notification_test

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.sp
import com.example.notification_test.ui.theme.NotificationtestTheme

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
NotificationtestTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
NotificationTest()
}
}
}
}
}
@Composable
fun NotificationTest() {
val context = LocalContext.current
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Button(onClick = { /*TODO*/
val notish = MyNotification(context, "タイトル", "これは通知テスト。")
notish.FirNotification()
}) {
Text(text = "通知を発生させるで?", fontSize = 16.sp)
}
}
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
NotificationtestTheme {
NotificationTest()
}
}

先人に感謝しかない。

5/8:さすがに notish.msg = "~~"みたいに直接書き換えるのは無理だった。

5/9:Good Night.

5/10:notish.notificationBilder.setContentText(変更内容)でテキスト内容を変更して

notish.notificationManager.notify(チャンネルID, notish.notificationBilder.build())で再表示できるみたいだ。再表示というか再通知っていう表現が正しいのかな。チャンネルIDは上記の”MyNotification.kt"の最後で100としてあるので100。

↑の参照元はこちら。

android-note.open-memo.net

 

上手くいきそうかな...?現状ではバックグラウンドで処理を行っていないため、タスクキルされてしまうとカウントダウンが強制終了されてしまう。この問題はAndroidのサービスとやらを上手く活用すれば良いらしい。頑張ろう。

そういえば通知の重要度設定に関するメモ。

developer.android.com

参考にした動画のコードだと、通知音+生える(・8・)なので、必要に応じて重要度設定を変更する。IMPORTANCE_HIGHってところを変える。ただしAndroid 8.0以上の場合の書き方だから、それ未満だと別の書き方を追加しないといけないのかな。

5/11:実機で色々試しているときに、ふと画面回転に対して何も対策をしていないことに気が付いた。これに関してはggれば直ぐに出てきた。助かる。

qiita.com

これ以外にも色々な操作パターンで試さないと未知の不具合が見つけられなさそうで面倒そうだ。。

17:30:”MyNotification.kt”内の notificationBiluder.setOngoing(Boolean) で通知がスワイプされた時に消えるか否かを設定できるようだ。Booleanがtrueなら消えないように設定が出来る。スワイプで消えても良いなら、特段Booleanをfalseに設定する必要はない。そもそもsetOngoing()を書かなければデフォルトで消せるようになっている。

5/12:全回復する日時表示をしてなかったな~と思って寄り道通知テストアプリで軽く試してみることに。

下から2番目がローカル時間で、1番下がローカル時間に1時間1分1秒を足したもの。コードはこんな感じ。

val time1: LocalTime? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
LocalTime.now()
} else {
TODO("VERSION.SDK_INT < O")
null
}
val timerCount: Long = 3661000L //1hour1minute1second

val time2: LocalTime? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
time1?.plusSeconds(timerCount/1000L)
} else {
TODO("VERSION.SDK_INT < O")
null
}

Androidのバージョンとか諸々で”非推奨です!使えません!”とかエラー表示が出てくるのが面倒。。

5/13:Googleさん的には定期的なタスク実行やバックグラウンド処理に対してWorkManagerを使うことを割と推奨している模様。

developer.android.com

developer.android.com

けど向いてない処理だったり使わない方がいいケースもあるようで。今回のやつはダメなのかな。鉄板実装方式はServiceなのか?

akira-watson.com

色々情報が出てきて難しいっすね。

なんか面白そうな機能が出てきたのでメモ。

developer.android.com

qiita.com

バックグラウンド処理ばかりに目が行っていた自分にはビックリの機能である。要は通知が常駐している(=ユーザが視認できる状況である)から、自分バックグラウンド処理じゃないっす!ってことなのかな。なかなか面白そう。けど更に調査を進めていると、こんな記事が。

qiita.com

Dozeモードとやらの存在。ZAZYの紙芝居じゃないよ。

OSアップデートに振り回されるエンジニアの気持ちが少し分かった気がする。。

5/14:左の鼻の穴だけやたら詰まる。寝起きとかヤバい。チョベリバ。

 

9/16:メンタル不調と思考が終わって製作を中断してたけど、ちょっとずつ再開していこうかな...?まぁ一番の理由は、このゲームのLP最大値が300であり、24時間放置で288回復なので、最悪このアプリが無くても24時間以内に1回は起動するやろ...という考えのせいで製作意欲がガタ落ちしたから。でもスクフェス2とかいうゲーム、LP全回復通知をまともに送れない仕様なのか、”全回復しました!”っていう偽通知を飛ばしてくる。分かってても毎回ドキッとするので、簡単に通知欄で現在LP値を見れるようにした方が精神的にいいような気がしてきた次第。

 久々にソースコード見たけど、結構内容を忘れてるもんですね。これはリハビリ期間が長くなりそうだ。

 

2024/03/31 15:19 なんかゲーム側がサービス終了してました。一体何だったのか。