Skip to content

歩数習得 16 #143

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 6, 2025
2 changes: 1 addition & 1 deletion .idea/other.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("org.jlleitschuh.gradle.ktlint")
id("com.google.devtools.ksp")
}

android {
Expand All @@ -10,7 +11,7 @@ android {

defaultConfig {
applicationId = "com.example.runningavater"
minSdk = 24
minSdk = 26
targetSdk = 35
versionCode = 1
versionName = "1.0"
Expand Down Expand Up @@ -85,4 +86,9 @@ dependencies {
implementation("com.github.PhilJay:MPAndroidChart:3.1.0")
implementation("io.coil-kt:coil-compose:2.7.0")
implementation("androidx.biometric:biometric:1.4.0-alpha02")
val room_version = "2.6.1"

implementation("androidx.room:room-runtime:$room_version")
ksp("androidx.room:room-compiler:$room_version")
implementation("androidx.room:room-ktx:$room_version")
}
11 changes: 11 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
<uses-permission android:name="android.permission.USE_BIOMETRIC" />


<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_HEALTH" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application
android:name=".MainApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand All @@ -29,6 +33,13 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<!-- フォアグラウンドサービス -->
<service
android:name=".StepCounterService"
android:enabled="true"
android:exported="true"
android:foregroundServiceType="health" />
</application>

</manifest>
30 changes: 28 additions & 2 deletions app/src/main/java/com/example/runningavater/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,22 +1,39 @@
package com.example.runningavater

import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import com.example.runningavater.ui.theme.RunningAvaterTheme

class MainActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
ContextCompat.checkSelfPermission(this, Manifest.permission.ACTIVITY_RECOGNITION) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACTIVITY_RECOGNITION),
1001,
)
}

startStepCounterService()

setContent {
RunningAvaterTheme {
// A surface container using the 'background' color from the theme
// 背景色をテーマから取得
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background,
Expand All @@ -26,4 +43,13 @@ class MainActivity : FragmentActivity() {
}
}
}

private fun startStepCounterService() {
val intent = Intent(this, StepCounterService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
} else {
startService(intent)
}
}
}
15 changes: 15 additions & 0 deletions app/src/main/java/com/example/runningavater/MainApplication.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.runningavater

import android.app.Application
import androidx.room.Room
import com.example.runningavater.db.AppDatabase

class MainApplication : Application() {
val db by lazy {
Room.databaseBuilder(
applicationContext,
AppDatabase::class.java,
"database-name",
).build()
}
}
148 changes: 148 additions & 0 deletions app/src/main/java/com/example/runningavater/StepCounterService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package com.example.runningavater

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.pm.ServiceInfo
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat
import com.example.runningavater.db.StepDate
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch

class StepCounterService : Service() {
private lateinit var sensorManager: SensorManager
private var stepSensor: Sensor? = null
private var totalSteps = 0

override fun onCreate() {
super.onCreate()

// sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
// stepSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
//
// if (stepSensor == null) {
// Log.e("StepCounterService", "Step Counter sensor not available!")
// stopSelf()
// }
//
//
// createNotificationChannel()
//
//
// val notification = NotificationCompat.Builder(this, "step_service_channel")
// .setContentTitle("Step Counter Service")
// .setContentText("Counting your steps...")
// .setSmallIcon(R.drawable.ic_steps)
// .build()
//
// startForeground(1, notification)
}

val coroutineScope = CoroutineScope(SupervisorJob())
val walkcount = Walkcount(this, coroutineScope)

override fun onStartCommand(
intent: Intent?,
flags: Int,
startId: Int,
): Int {
createNotificationChannel()
val notification =
NotificationCompat.Builder(this, "step_service_channel")
.setContentTitle("Step Counter Service")
.setContentText("Counting your steps...")
.setSmallIcon(R.drawable.app_icon_yellow)
.build()
ServiceCompat.startForeground(
this,
100,
notification,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH
} else {
0
},
)
startcount(this, walkcount)
// stepSensor?.let {
// sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_NORMAL)
// }
return START_STICKY
}

override fun onDestroy() {
super.onDestroy()
stopcount(this, walkcount)
// sensorManager.unregisterListener(this)
coroutineScope.cancel()
}

override fun onBind(intent: Intent?): IBinder? = null

// override fun onSensorChanged(event: SensorEvent?) {
// if (event?.sensor?.type == Sensor.TYPE_STEP_COUNTER) {
// val steps = event.values[0].toInt()
// Log.d("StepCounterService", "Steps: $steps")
// totalSteps = steps
// }
// }
//
// override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}

private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel =
NotificationChannel(
"step_service_channel",
"Step Counter Service",
NotificationManager.IMPORTANCE_LOW,
)
val manager = getSystemService(NotificationManager::class.java)
manager?.createNotificationChannel(channel)
}
}
}

fun startcount(
context: Context,
walkcount: Walkcount,
) {
val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR)
sensorManager.registerListener(walkcount, sensor, SensorManager.SENSOR_DELAY_NORMAL)
}

class Walkcount(val context: Context, val coroutineScope: CoroutineScope) : SensorEventListener {
override fun onSensorChanged(p0: SensorEvent?) {
coroutineScope.launch(Dispatchers.IO) {
val app = context.applicationContext as MainApplication
app.db.stepDateDao().insertAll(StepDate(id = 0, System.currentTimeMillis()))
}
}

override fun onAccuracyChanged(
p0: Sensor?,
p1: Int,
) {
}
}

fun stopcount(
context: Context,
walkcount: Walkcount,
) {
val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensorManager.unregisterListener(walkcount)
}
9 changes: 9 additions & 0 deletions app/src/main/java/com/example/runningavater/db/AppDatabase.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.runningavater.db

import androidx.room.Database
import androidx.room.RoomDatabase

@Database(entities = [StepDate::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun stepDateDao(): StepDateDao
}
10 changes: 10 additions & 0 deletions app/src/main/java/com/example/runningavater/db/StepDate.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.example.runningavater.db

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "step_data")
data class StepDate(
@PrimaryKey(autoGenerate = true) val id: Long,
val day: Long,
)
17 changes: 17 additions & 0 deletions app/src/main/java/com/example/runningavater/db/StepDateDao.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.example.runningavater.db

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query

@Dao
interface StepDateDao {
@Insert
fun insertAll(vararg stepDate: StepDate)

@Query("SELECT COUNT(*) from step_data WHERE day >= :start and day <= :end")
fun getTotalWalk(
start: Long,
end: Long,
): Int
}
40 changes: 39 additions & 1 deletion app/src/main/java/com/example/runningavater/home/HomeScreen.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example.runningavater.home

import android.content.Intent
import android.graphics.BitmapFactory
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
Expand All @@ -25,8 +26,14 @@ import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.runningavater.MainApplication
import com.example.runningavater.StepCounterService
import com.example.runningavater.ui.theme.NuclearMango
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.time.LocalDateTime
import java.time.ZoneId
import java.util.Date
import java.util.Locale

Expand All @@ -43,6 +50,33 @@ fun HomeScreen() {
kotlinx.coroutines.delay(60000L) // 1分ごとに更新
}
}
val context = LocalContext.current
LaunchedEffect(key1 = Unit) {
val intent = Intent(context, StepCounterService::class.java)
context.startForegroundService(intent)
}
val stepcount = remember { mutableStateOf<Int?>(null) }
LaunchedEffect(key1 = Unit) {
val now = LocalDateTime.now() // 2025/02/23 23:52:10.123

val todayStart =
now
.withHour(0) // 2025/02/23 00:52:10.123
.withMinute(0) // 2025/02/23 00:00:10.123
.withSecond(0) // 2025/02/23 00:00:00.123
.withNano(0) // 2025/02/23 00:00:00.000000

val todayEnd =
now
.withHour(23) // 2025/02/23 00:52:10.123
.withMinute(59) // 2025/02/23 00:00:10.123
.withSecond(59) // 2025/02/23 00:00:00.123
.withNano(999999999) // 2025/02/23 00:00:00.000000
launch(Dispatchers.IO) {
val app = context.applicationContext as MainApplication
stepcount.value = app.db.stepDateDao().getTotalWalk(todayStart.toEpochMillis(), todayEnd.toEpochMillis())
}
}
Scaffold(
topBar = {
// ヘッダーの表示
Expand Down Expand Up @@ -75,7 +109,7 @@ fun HomeScreen() {

// 歩数の数字の部分
Text(
text = "35",
stepcount.value.toString(),
fontSize = 96.sp,
color = NuclearMango,
)
Expand Down Expand Up @@ -121,3 +155,7 @@ fun SetImage(
modifier = modifier,
)
}

fun LocalDateTime.toEpochMillis(zoneId: ZoneId = ZoneId.systemDefault()): Long {
return this.atZone(zoneId).toInstant().toEpochMilli()
}
Loading