Skip to content

Commit 1445904

Browse files
authored
Merge pull request #116 from NID-kt/feature/issue-52
Feature/issue 52 [初期フロー ねこもち担当]
2 parents 4b0a0b3 + a2b5d23 commit 1445904

17 files changed

+785
-27
lines changed

app/build.gradle.kts

+2
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ dependencies {
7474
implementation("androidx.appcompat:appcompat:1.7.0")
7575
implementation("com.google.accompanist:accompanist-permissions:0.34.0")
7676
implementation("androidx.navigation:navigation-compose:2.7.7")
77+
implementation("io.coil-kt.coil3:coil-gif:3.0.3")
7778
testImplementation("junit:junit:4.13.2")
7879
androidTestImplementation("androidx.test.ext:junit:1.2.1")
7980
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
@@ -83,4 +84,5 @@ dependencies {
8384
debugImplementation("androidx.compose.ui:ui-test-manifest")
8485
implementation("com.github.PhilJay:MPAndroidChart:3.1.0")
8586
implementation("io.coil-kt:coil-compose:2.7.0")
87+
implementation("androidx.biometric:biometric:1.4.0-alpha02")
8688
}

app/src/main/AndroidManifest.xml

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
<?xml version="1.0" encoding="utf-8"?>
22

33

4-
54
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
65
xmlns:tools="http://schemas.android.com/tools">
6+
77
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
8+
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
89

910

1011
<application
@@ -30,4 +31,4 @@
3031
</activity>
3132
</application>
3233

33-
</manifest>
34+
</manifest>

app/src/main/java/com/example/runningavater/MainActivity.kt

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
package com.example.runningavater
2+
23
import android.os.Bundle
3-
import androidx.activity.ComponentActivity
44
import androidx.activity.compose.setContent
55
import androidx.compose.foundation.background
66
import androidx.compose.foundation.layout.fillMaxSize
77
import androidx.compose.material3.MaterialTheme
88
import androidx.compose.material3.Surface
99
import androidx.compose.ui.Modifier
10+
import androidx.fragment.app.FragmentActivity
1011
import com.example.runningavater.ui.theme.RunningAvaterTheme
1112

12-
class MainActivity : ComponentActivity() {
13+
class MainActivity : FragmentActivity() {
1314
override fun onCreate(savedInstanceState: Bundle?) {
1415
super.onCreate(savedInstanceState)
1516
setContent {

app/src/main/java/com/example/runningavater/MyAppNavHost.kt

+8-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import androidx.navigation.compose.NavHost
2727
import androidx.navigation.compose.composable
2828
import androidx.navigation.compose.currentBackStackEntryAsState
2929
import androidx.navigation.compose.rememberNavController
30+
import com.example.runningavater.authentication.AuthenticationScreen
3031
import com.example.runningavater.growth.GrowthScreen
3132
import com.example.runningavater.home.HomeScreen
3233
import com.example.runningavater.initialFlow.initialFlow
@@ -43,13 +44,19 @@ fun MyAppNavHost(
4344
) {
4445
val navBackStackEntry by navController.currentBackStackEntryAsState()
4546
val currentDestination = navBackStackEntry?.destination
47+
val startDestination = if (true) "initialFlow/2" else "authentication"
4648
Scaffold(
4749
bottomBar = {
48-
MainBottomBar(currentDestination, navController)
50+
if (currentDestination?.route?.startsWith("initialFlow") != true) {
51+
MainBottomBar(currentDestination, navController)
52+
}
4953
},
5054
) { paddingValues ->
5155
// ナビゲーションホストを作成
5256
NavHost(navController = navController, startDestination = startDestination, modifier = Modifier.padding(paddingValues)) {
57+
composable("authentication") {
58+
AuthenticationScreen(navController = navController) // 認証画面を表示
59+
}
5360
composable("home") {
5461
HomeScreen() // メイン画面を表示
5562
}
@@ -141,6 +148,5 @@ val Context.settingsDataStore by preferencesDataStore(name = "settings")
141148
class MyApplication : Application() {
142149
override fun onCreate() {
143150
super.onCreate()
144-
// アプリケーションの初期化構造
145151
}
146152
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package com.example.runningavater.authentication
2+
3+
import android.content.Context
4+
import android.content.Intent
5+
import android.os.Build
6+
import android.provider.Settings
7+
import android.widget.Toast
8+
import androidx.biometric.BiometricManager
9+
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
10+
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
11+
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
12+
import androidx.biometric.BiometricPrompt
13+
import androidx.compose.material3.CircularProgressIndicator
14+
import androidx.compose.runtime.Composable
15+
import androidx.compose.runtime.LaunchedEffect
16+
import androidx.compose.ui.Modifier
17+
import androidx.compose.ui.platform.LocalContext
18+
import androidx.core.content.ContextCompat
19+
import androidx.fragment.app.FragmentActivity
20+
import androidx.navigation.NavController
21+
22+
@Composable
23+
fun AuthenticationScreen(
24+
navController: NavController,
25+
modifier: Modifier = Modifier,
26+
) {
27+
val context = LocalContext.current
28+
LaunchedEffect(key1 = Unit) {
29+
when (BiometricManager.from(context).canAuthenticate(BIOMETRIC_STRONG or BIOMETRIC_WEAK or DEVICE_CREDENTIAL)) {
30+
BiometricManager.BIOMETRIC_SUCCESS -> { // 生体認証が利用可能
31+
showAuthenticationDialog(context, navController)
32+
}
33+
34+
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> { // 生体情報が端末に登録されていない
35+
val enrollIntent =
36+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
37+
Intent(Settings.ACTION_BIOMETRIC_ENROLL).apply {
38+
putExtra(
39+
Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
40+
BIOMETRIC_STRONG or DEVICE_CREDENTIAL,
41+
)
42+
}
43+
} else {
44+
TODO("VERSION.SDK_INT < R")
45+
}
46+
context.startActivity(enrollIntent)
47+
}
48+
49+
BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE, BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> { // 生体認証ハードウェアが利用不可
50+
TODO("ダイアログを表示")
51+
}
52+
53+
else -> throw IllegalStateException("ここには入らないはず。")
54+
}
55+
}
56+
CircularProgressIndicator()
57+
}
58+
59+
fun showAuthenticationDialog(
60+
context: Context,
61+
navController: NavController,
62+
) {
63+
val biometricPrompt =
64+
BiometricPrompt(
65+
context as FragmentActivity,
66+
ContextCompat.getMainExecutor(context),
67+
object : BiometricPrompt.AuthenticationCallback() {
68+
override fun onAuthenticationError(
69+
errorCode: Int,
70+
errString: CharSequence,
71+
) {
72+
super.onAuthenticationError(errorCode, errString)
73+
Toast
74+
.makeText(
75+
context,
76+
"Authentication error: $errString",
77+
Toast.LENGTH_SHORT,
78+
).show()
79+
}
80+
81+
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
82+
super.onAuthenticationSucceeded(result)
83+
navController.navigate("home") {
84+
// これにより、「ホーム」に戻るときにバックスタックがクリアされる
85+
popUpTo(navController.graph.startDestinationId) {
86+
saveState = true
87+
}
88+
launchSingleTop = true // 同じ画面を複数スタックしない
89+
restoreState = true // 前の状態を復元
90+
}
91+
}
92+
93+
override fun onAuthenticationFailed() {
94+
super.onAuthenticationFailed()
95+
Toast
96+
.makeText(
97+
context,
98+
"Authentication failed",
99+
Toast.LENGTH_SHORT,
100+
).show()
101+
}
102+
},
103+
)
104+
105+
val promptInfo =
106+
BiometricPrompt.PromptInfo
107+
.Builder()
108+
.setTitle("Biometric login for my app")
109+
.setSubtitle("Log in using your biometric credential")
110+
.setAllowedAuthenticators(BIOMETRIC_STRONG or BIOMETRIC_WEAK or DEVICE_CREDENTIAL)
111+
.build()
112+
biometricPrompt.authenticate(promptInfo)
113+
}
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,91 @@
11
package com.example.runningavater.initialFlow
22

3+
import androidx.compose.foundation.Image
4+
import androidx.compose.foundation.layout.Box
5+
import androidx.compose.foundation.layout.Column
6+
import androidx.compose.foundation.layout.aspectRatio
7+
import androidx.compose.foundation.layout.fillMaxSize
8+
import androidx.compose.foundation.layout.fillMaxWidth
9+
import androidx.compose.foundation.layout.padding
10+
import androidx.compose.material3.Text
311
import androidx.compose.runtime.Composable
12+
import androidx.compose.ui.Alignment
13+
import androidx.compose.ui.Modifier
14+
import androidx.compose.ui.graphics.Color
15+
import androidx.compose.ui.layout.ContentScale
16+
import androidx.compose.ui.res.painterResource
17+
import androidx.compose.ui.text.style.TextAlign
18+
import androidx.compose.ui.tooling.preview.Preview
19+
import androidx.compose.ui.unit.dp
20+
import androidx.compose.ui.unit.sp
21+
import androidx.compose.ui.zIndex
22+
import androidx.navigation.NavHostController
23+
import androidx.navigation.compose.rememberNavController
24+
import com.example.runningavater.R
425
import com.example.runningavater.initialFlow.components.InitialFlowBackground
26+
import com.example.runningavater.initialFlow.components.NextButton
27+
import com.example.runningavater.ui.theme.RunningAvaterTheme
528

629
@Composable
7-
fun InitialFlow10Screen() {
30+
fun InitialFlow10Screen(navController: NavHostController) {
831
InitialFlowBackground {
32+
Box(modifier = Modifier.fillMaxSize()) {
33+
Column(
34+
modifier =
35+
Modifier
36+
.fillMaxWidth()
37+
.align(Alignment.TopCenter)
38+
.padding(top = 48.dp)
39+
.zIndex(1f),
40+
horizontalAlignment = Alignment.CenterHorizontally,
41+
) {
42+
Text(
43+
text = "さっそくダイエットを\n始めよう!",
44+
color = Color.Black,
45+
fontSize = 32.sp,
46+
textAlign = TextAlign.Center,
47+
lineHeight = 46.3.sp,
48+
)
49+
}
50+
Column(
51+
modifier =
52+
Modifier
53+
.fillMaxWidth()
54+
.align(Alignment.BottomCenter),
55+
) {
56+
Image(
57+
painter = painterResource(id = R.drawable.initialflow10),
58+
contentDescription = "big bear",
59+
modifier =
60+
Modifier
61+
.fillMaxWidth()
62+
.aspectRatio(0.55f)
63+
.zIndex(-1f),
64+
contentScale = ContentScale.Crop,
65+
)
66+
//
67+
}
68+
Column(
69+
modifier =
70+
Modifier
71+
.align(Alignment.BottomEnd)
72+
.padding(0.dp, 0.dp, 0.dp, 20.dp),
73+
) {
74+
NextButton(
75+
navController = navController,
76+
nextDestination = "authentication",
77+
text = "始める",
78+
modifier = Modifier.padding(20.dp, 0.dp, 20.dp, 10.dp),
79+
)
80+
}
81+
}
82+
}
83+
}
84+
85+
@Preview
86+
@Composable
87+
private fun InitialFlow10Preview() {
88+
RunningAvaterTheme {
89+
InitialFlow10Screen(rememberNavController())
990
}
1091
}
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,93 @@
11
package com.example.runningavater.initialFlow
22

3+
import androidx.compose.foundation.Image
4+
import androidx.compose.foundation.layout.Box
5+
import androidx.compose.foundation.layout.Column
6+
import androidx.compose.foundation.layout.aspectRatio
7+
import androidx.compose.foundation.layout.fillMaxSize
8+
import androidx.compose.foundation.layout.fillMaxWidth
9+
import androidx.compose.foundation.layout.padding
10+
import androidx.compose.material3.Text
311
import androidx.compose.runtime.Composable
12+
import androidx.compose.ui.Alignment
13+
import androidx.compose.ui.Modifier
14+
import androidx.compose.ui.graphics.Color
15+
import androidx.compose.ui.res.painterResource
16+
import androidx.compose.ui.text.style.TextAlign
17+
import androidx.compose.ui.tooling.preview.Preview
18+
import androidx.compose.ui.unit.dp
19+
import androidx.compose.ui.unit.sp
20+
import androidx.navigation.NavHostController
21+
import androidx.navigation.compose.rememberNavController
22+
import com.example.runningavater.R
23+
import com.example.runningavater.initialFlow.components.BackButton
424
import com.example.runningavater.initialFlow.components.InitialFlowBackground
25+
import com.example.runningavater.initialFlow.components.NextButton
26+
import com.example.runningavater.ui.theme.RunningAvaterTheme
527

628
@Composable
7-
fun InitialFlow2Screen() {
29+
fun InitialFlow2Screen(navController: NavHostController) {
830
InitialFlowBackground {
31+
Box(
32+
modifier = Modifier.fillMaxSize(),
33+
) {
34+
Column(
35+
modifier =
36+
Modifier
37+
.fillMaxWidth()
38+
.align(Alignment.TopCenter)
39+
.padding(0.dp, 50.dp, 0.dp, 0.dp),
40+
horizontalAlignment = Alignment.CenterHorizontally,
41+
) {
42+
Text(
43+
text = "可愛いアバターと一緒に\n楽しくダイエット",
44+
color = Color.Black,
45+
fontSize = 32.sp,
46+
lineHeight = 46.3.sp,
47+
textAlign = TextAlign.Center,
48+
)
49+
}
50+
val image = painterResource(R.drawable.initialflow2)
51+
Image(
52+
painter = image,
53+
contentDescription = null,
54+
modifier =
55+
Modifier
56+
.fillMaxWidth()
57+
.aspectRatio(image.intrinsicSize.width / image.intrinsicSize.height)
58+
.align(Alignment.Center),
59+
)
60+
61+
Column(
62+
modifier =
63+
Modifier
64+
.align(Alignment.BottomStart)
65+
.padding(0.dp, 0.dp, 0.dp, 80.dp),
66+
) {
67+
NextButton(
68+
navController = navController,
69+
nextDestination = "initialFlow/5",
70+
modifier = Modifier.padding(20.dp, 0.dp, 20.dp, 10.dp),
71+
)
72+
}
73+
Column(
74+
modifier =
75+
Modifier
76+
.align(Alignment.BottomEnd)
77+
.padding(20.dp),
78+
) {
79+
BackButton(
80+
navController = navController,
81+
)
82+
}
83+
}
84+
}
85+
}
86+
87+
@Preview
88+
@Composable
89+
private fun InitialFlow2Preview() {
90+
RunningAvaterTheme {
91+
InitialFlow2Screen(rememberNavController())
992
}
1093
}

0 commit comments

Comments
 (0)