Skip to content

Commit 511fe06

Browse files
authored
Merge pull request #143 from NID-kt/issue-16
歩数習得 16
2 parents d0acb5d + 0f64eeb commit 511fe06

File tree

12 files changed

+293
-11
lines changed

12 files changed

+293
-11
lines changed

.idea/other.xml

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/build.gradle.kts

+7-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ plugins {
22
id("com.android.application")
33
id("org.jetbrains.kotlin.android")
44
id("org.jlleitschuh.gradle.ktlint")
5+
id("com.google.devtools.ksp")
56
}
67

78
android {
@@ -10,7 +11,7 @@ android {
1011

1112
defaultConfig {
1213
applicationId = "com.example.runningavater"
13-
minSdk = 24
14+
minSdk = 26
1415
targetSdk = 35
1516
versionCode = 1
1617
versionName = "1.0"
@@ -85,4 +86,9 @@ dependencies {
8586
implementation("com.github.PhilJay:MPAndroidChart:3.1.0")
8687
implementation("io.coil-kt:coil-compose:2.7.0")
8788
implementation("androidx.biometric:biometric:1.4.0-alpha02")
89+
val room_version = "2.6.1"
90+
91+
implementation("androidx.room:room-runtime:$room_version")
92+
ksp("androidx.room:room-compiler:$room_version")
93+
implementation("androidx.room:room-ktx:$room_version")
8894
}

app/src/main/AndroidManifest.xml

+11
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
99

1010

11+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
12+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_HEALTH" />
13+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
1114
<application
15+
android:name=".MainApplication"
1216
android:allowBackup="true"
1317
android:dataExtractionRules="@xml/data_extraction_rules"
1418
android:fullBackupContent="@xml/backup_rules"
@@ -29,6 +33,13 @@
2933
<category android:name="android.intent.category.LAUNCHER" />
3034
</intent-filter>
3135
</activity>
36+
37+
<!-- フォアグラウンドサービス -->
38+
<service
39+
android:name=".StepCounterService"
40+
android:enabled="true"
41+
android:exported="true"
42+
android:foregroundServiceType="health" />
3243
</application>
3344

3445
</manifest>
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,39 @@
11
package com.example.runningavater
22

3+
import android.Manifest
4+
import android.content.Intent
5+
import android.content.pm.PackageManager
6+
import android.os.Build
37
import android.os.Bundle
48
import androidx.activity.compose.setContent
59
import androidx.compose.foundation.background
610
import androidx.compose.foundation.layout.fillMaxSize
711
import androidx.compose.material3.MaterialTheme
812
import androidx.compose.material3.Surface
913
import androidx.compose.ui.Modifier
14+
import androidx.core.app.ActivityCompat
15+
import androidx.core.content.ContextCompat
1016
import androidx.fragment.app.FragmentActivity
1117
import com.example.runningavater.ui.theme.RunningAvaterTheme
1218

1319
class MainActivity : FragmentActivity() {
1420
override fun onCreate(savedInstanceState: Bundle?) {
1521
super.onCreate(savedInstanceState)
22+
23+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
24+
ContextCompat.checkSelfPermission(this, Manifest.permission.ACTIVITY_RECOGNITION) != PackageManager.PERMISSION_GRANTED
25+
) {
26+
ActivityCompat.requestPermissions(
27+
this,
28+
arrayOf(Manifest.permission.ACTIVITY_RECOGNITION),
29+
1001,
30+
)
31+
}
32+
33+
startStepCounterService()
34+
1635
setContent {
1736
RunningAvaterTheme {
18-
// A surface container using the 'background' color from the theme
19-
// 背景色をテーマから取得
2037
Surface(
2138
modifier = Modifier.fillMaxSize(),
2239
color = MaterialTheme.colorScheme.background,
@@ -26,4 +43,13 @@ class MainActivity : FragmentActivity() {
2643
}
2744
}
2845
}
46+
47+
private fun startStepCounterService() {
48+
val intent = Intent(this, StepCounterService::class.java)
49+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
50+
startForegroundService(intent)
51+
} else {
52+
startService(intent)
53+
}
54+
}
2955
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.example.runningavater
2+
3+
import android.app.Application
4+
import androidx.room.Room
5+
import com.example.runningavater.db.AppDatabase
6+
7+
class MainApplication : Application() {
8+
val db by lazy {
9+
Room.databaseBuilder(
10+
applicationContext,
11+
AppDatabase::class.java,
12+
"database-name",
13+
).build()
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package com.example.runningavater
2+
3+
import android.app.NotificationChannel
4+
import android.app.NotificationManager
5+
import android.app.Service
6+
import android.content.Context
7+
import android.content.Intent
8+
import android.content.pm.ServiceInfo
9+
import android.hardware.Sensor
10+
import android.hardware.SensorEvent
11+
import android.hardware.SensorEventListener
12+
import android.hardware.SensorManager
13+
import android.os.Build
14+
import android.os.IBinder
15+
import androidx.core.app.NotificationCompat
16+
import androidx.core.app.ServiceCompat
17+
import com.example.runningavater.db.StepDate
18+
import kotlinx.coroutines.CoroutineScope
19+
import kotlinx.coroutines.Dispatchers
20+
import kotlinx.coroutines.SupervisorJob
21+
import kotlinx.coroutines.cancel
22+
import kotlinx.coroutines.launch
23+
24+
class StepCounterService : Service() {
25+
private lateinit var sensorManager: SensorManager
26+
private var stepSensor: Sensor? = null
27+
private var totalSteps = 0
28+
29+
override fun onCreate() {
30+
super.onCreate()
31+
32+
// sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
33+
// stepSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
34+
//
35+
// if (stepSensor == null) {
36+
// Log.e("StepCounterService", "Step Counter sensor not available!")
37+
// stopSelf()
38+
// }
39+
//
40+
//
41+
// createNotificationChannel()
42+
//
43+
//
44+
// val notification = NotificationCompat.Builder(this, "step_service_channel")
45+
// .setContentTitle("Step Counter Service")
46+
// .setContentText("Counting your steps...")
47+
// .setSmallIcon(R.drawable.ic_steps)
48+
// .build()
49+
//
50+
// startForeground(1, notification)
51+
}
52+
53+
val coroutineScope = CoroutineScope(SupervisorJob())
54+
val walkcount = Walkcount(this, coroutineScope)
55+
56+
override fun onStartCommand(
57+
intent: Intent?,
58+
flags: Int,
59+
startId: Int,
60+
): Int {
61+
createNotificationChannel()
62+
val notification =
63+
NotificationCompat.Builder(this, "step_service_channel")
64+
.setContentTitle("Step Counter Service")
65+
.setContentText("Counting your steps...")
66+
.setSmallIcon(R.drawable.app_icon_yellow)
67+
.build()
68+
ServiceCompat.startForeground(
69+
this,
70+
100,
71+
notification,
72+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
73+
ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH
74+
} else {
75+
0
76+
},
77+
)
78+
startcount(this, walkcount)
79+
// stepSensor?.let {
80+
// sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_NORMAL)
81+
// }
82+
return START_STICKY
83+
}
84+
85+
override fun onDestroy() {
86+
super.onDestroy()
87+
stopcount(this, walkcount)
88+
// sensorManager.unregisterListener(this)
89+
coroutineScope.cancel()
90+
}
91+
92+
override fun onBind(intent: Intent?): IBinder? = null
93+
94+
// override fun onSensorChanged(event: SensorEvent?) {
95+
// if (event?.sensor?.type == Sensor.TYPE_STEP_COUNTER) {
96+
// val steps = event.values[0].toInt()
97+
// Log.d("StepCounterService", "Steps: $steps")
98+
// totalSteps = steps
99+
// }
100+
// }
101+
//
102+
// override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
103+
104+
private fun createNotificationChannel() {
105+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
106+
val channel =
107+
NotificationChannel(
108+
"step_service_channel",
109+
"Step Counter Service",
110+
NotificationManager.IMPORTANCE_LOW,
111+
)
112+
val manager = getSystemService(NotificationManager::class.java)
113+
manager?.createNotificationChannel(channel)
114+
}
115+
}
116+
}
117+
118+
fun startcount(
119+
context: Context,
120+
walkcount: Walkcount,
121+
) {
122+
val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
123+
val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR)
124+
sensorManager.registerListener(walkcount, sensor, SensorManager.SENSOR_DELAY_NORMAL)
125+
}
126+
127+
class Walkcount(val context: Context, val coroutineScope: CoroutineScope) : SensorEventListener {
128+
override fun onSensorChanged(p0: SensorEvent?) {
129+
coroutineScope.launch(Dispatchers.IO) {
130+
val app = context.applicationContext as MainApplication
131+
app.db.stepDateDao().insertAll(StepDate(id = 0, System.currentTimeMillis()))
132+
}
133+
}
134+
135+
override fun onAccuracyChanged(
136+
p0: Sensor?,
137+
p1: Int,
138+
) {
139+
}
140+
}
141+
142+
fun stopcount(
143+
context: Context,
144+
walkcount: Walkcount,
145+
) {
146+
val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
147+
sensorManager.unregisterListener(walkcount)
148+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.example.runningavater.db
2+
3+
import androidx.room.Database
4+
import androidx.room.RoomDatabase
5+
6+
@Database(entities = [StepDate::class], version = 1)
7+
abstract class AppDatabase : RoomDatabase() {
8+
abstract fun stepDateDao(): StepDateDao
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.example.runningavater.db
2+
3+
import androidx.room.Entity
4+
import androidx.room.PrimaryKey
5+
6+
@Entity(tableName = "step_data")
7+
data class StepDate(
8+
@PrimaryKey(autoGenerate = true) val id: Long,
9+
val day: Long,
10+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.example.runningavater.db
2+
3+
import androidx.room.Dao
4+
import androidx.room.Insert
5+
import androidx.room.Query
6+
7+
@Dao
8+
interface StepDateDao {
9+
@Insert
10+
fun insertAll(vararg stepDate: StepDate)
11+
12+
@Query("SELECT COUNT(*) from step_data WHERE day >= :start and day <= :end")
13+
fun getTotalWalk(
14+
start: Long,
15+
end: Long,
16+
): Int
17+
}

app/src/main/java/com/example/runningavater/home/HomeScreen.kt

+39-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.example.runningavater.home
22

3+
import android.content.Intent
34
import android.graphics.BitmapFactory
45
import androidx.compose.foundation.Image
56
import androidx.compose.foundation.layout.Box
@@ -25,8 +26,14 @@ import androidx.compose.ui.graphics.asImageBitmap
2526
import androidx.compose.ui.platform.LocalContext
2627
import androidx.compose.ui.unit.dp
2728
import androidx.compose.ui.unit.sp
29+
import com.example.runningavater.MainApplication
30+
import com.example.runningavater.StepCounterService
2831
import com.example.runningavater.ui.theme.NuclearMango
32+
import kotlinx.coroutines.Dispatchers
33+
import kotlinx.coroutines.launch
2934
import java.text.SimpleDateFormat
35+
import java.time.LocalDateTime
36+
import java.time.ZoneId
3037
import java.util.Date
3138
import java.util.Locale
3239

@@ -43,6 +50,33 @@ fun HomeScreen() {
4350
kotlinx.coroutines.delay(60000L) // 1分ごとに更新
4451
}
4552
}
53+
val context = LocalContext.current
54+
LaunchedEffect(key1 = Unit) {
55+
val intent = Intent(context, StepCounterService::class.java)
56+
context.startForegroundService(intent)
57+
}
58+
val stepcount = remember { mutableStateOf<Int?>(null) }
59+
LaunchedEffect(key1 = Unit) {
60+
val now = LocalDateTime.now() // 2025/02/23 23:52:10.123
61+
62+
val todayStart =
63+
now
64+
.withHour(0) // 2025/02/23 00:52:10.123
65+
.withMinute(0) // 2025/02/23 00:00:10.123
66+
.withSecond(0) // 2025/02/23 00:00:00.123
67+
.withNano(0) // 2025/02/23 00:00:00.000000
68+
69+
val todayEnd =
70+
now
71+
.withHour(23) // 2025/02/23 00:52:10.123
72+
.withMinute(59) // 2025/02/23 00:00:10.123
73+
.withSecond(59) // 2025/02/23 00:00:00.123
74+
.withNano(999999999) // 2025/02/23 00:00:00.000000
75+
launch(Dispatchers.IO) {
76+
val app = context.applicationContext as MainApplication
77+
stepcount.value = app.db.stepDateDao().getTotalWalk(todayStart.toEpochMillis(), todayEnd.toEpochMillis())
78+
}
79+
}
4680
Scaffold(
4781
topBar = {
4882
// ヘッダーの表示
@@ -75,7 +109,7 @@ fun HomeScreen() {
75109

76110
// 歩数の数字の部分
77111
Text(
78-
text = "35",
112+
stepcount.value.toString(),
79113
fontSize = 96.sp,
80114
color = NuclearMango,
81115
)
@@ -121,3 +155,7 @@ fun SetImage(
121155
modifier = modifier,
122156
)
123157
}
158+
159+
fun LocalDateTime.toEpochMillis(zoneId: ZoneId = ZoneId.systemDefault()): Long {
160+
return this.atZone(zoneId).toInstant().toEpochMilli()
161+
}

0 commit comments

Comments
 (0)