Skip to content

Commit c96188e

Browse files
committed
add NFC features
1 parent 9cbdeb5 commit c96188e

File tree

106 files changed

+9097
-55
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

106 files changed

+9097
-55
lines changed

app/build.gradle.kts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ android {
1818
applicationId = "com.nativeapptemplate.nativeapptemplatefree"
1919
targetSdk = 35
2020
minSdk = 26
21-
versionCode = 1
22-
versionName = "1.0.0"
21+
versionCode = 2
22+
versionName = "2.0.0"
2323

2424
vectorDrawables {
2525
useSupportLibrary = true
@@ -140,10 +140,13 @@ dependencies {
140140
implementation(libs.androidx.navigation.compose)
141141
implementation(libs.androidx.profileinstaller)
142142
implementation(libs.androidx.tracing.ktx)
143+
implementation(libs.capturable)
144+
implementation(libs.compose.qr.code)
143145
implementation(libs.hilt.android)
144146
implementation(libs.kotlin.stdlib.jdk8)
145147
implementation(libs.kotlinx.coroutines.guava)
146148
implementation(libs.kotlinx.serialization.json)
149+
implementation(libs.lottie.compose)
147150
implementation(libs.okhttp)
148151
implementation(libs.okhttp.logging.interceptor)
149152
implementation(libs.retrofit)

app/src/main/AndroidManifest.xml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
<uses-permission android:name="android.permission.INTERNET" />
66
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
77

8+
<uses-permission android:name="android.permission.NFC" />
9+
<uses-feature android:name="android.hardware.nfc" android:required="false" />
10+
811
<application
912
android:name=".NativeAppTemplateApplication"
1013
android:allowBackup="true"
@@ -18,6 +21,10 @@
1821
tools:targetApi="tiramisu">
1922
<profileable android:shell="true" tools:targetApi="q" />
2023

24+
<!--
25+
`singleTask` for Background Tag Reading. Avoid running MainActivity onCreate again with Background Tag Reading.
26+
https://qiita.com/takagimeow/items/48b37c55ad8d73d5da88
27+
-->
2128
<activity
2229
android:name=".MainActivity"
2330
android:exported="true"
@@ -34,6 +41,23 @@
3441
<data android:scheme="mailto" />
3542
<category android:name="android.intent.category.DEFAULT" />
3643
</intent-filter>
44+
<intent-filter>
45+
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
46+
<category android:name="android.intent.category.DEFAULT" />
47+
<data android:scheme="https"
48+
android:host="api.nativeapptemplate.com"
49+
android:path="/scan"
50+
/>
51+
</intent-filter>
3752
</activity>
53+
<provider
54+
android:name="androidx.core.content.FileProvider"
55+
android:authorities="${applicationId}.fileprovider"
56+
android:grantUriPermissions="true"
57+
android:exported="false">
58+
<meta-data
59+
android:name="android.support.FILE_PROVIDER_PATHS"
60+
android:resource="@xml/filepaths" />
61+
</provider>
3862
</application>
3963
</manifest>

app/src/main/assets/item_tag.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"data": {
3+
"id": "9712F2DF-DFC7-A3AA-66BC-191203654A1A",
4+
"type": "item_tag",
5+
"attributes": {
6+
"shop_id": "5712F2DF-DFC7-A3AA-66BC-191203654A1A",
7+
"queue_number": "A001",
8+
"state": "idled",
9+
"scan_state": "unscanned",
10+
"created_at": "2025-01-02T12:00:00.000Z",
11+
"shop_name": "8th & Townsend",
12+
"customer_read_at": "2025-01-02T12:00:01.000Z",
13+
"completed_at": "2025-01-02T12:00:03.000Z",
14+
"already_completed": false
15+
}
16+
}
17+
}

app/src/main/assets/item_tags.json

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"data": [
3+
{
4+
"id": "9712F2DF-DFC7-A3AA-66BC-191203654A1A",
5+
"type": "item_tag",
6+
"attributes": {
7+
"shop_id": "5712F2DF-DFC7-A3AA-66BC-191203654A1A",
8+
"queue_number": "A001",
9+
"state": "idled",
10+
"scan_state": "unscanned",
11+
"created_at": "2025-01-02T12:00:00.000Z",
12+
"shop_name": "8th & Townsend",
13+
"customer_read_at": "2025-01-02T12:00:01.000Z",
14+
"completed_at": "2025-01-02T12:00:03.000Z",
15+
"already_completed": false
16+
}
17+
},
18+
{
19+
"id": "9712F2DF-DFC7-A3AA-66BC-191203654A1",
20+
"type": "shop",
21+
"attributes": {
22+
"shop_id": "5712F2DF-DFC7-A3AA-66BC-191203654A1A",
23+
"queue_number": "A002",
24+
"state": "idled",
25+
"scan_state": "unscanned",
26+
"created_at": "2025-01-02T12:00:00.000Z",
27+
"shop_name": "8th & Townsend",
28+
"customer_read_at": "2025-01-02T12:00:01.000Z",
29+
"completed_at": "2025-01-02T12:00:03.000Z",
30+
"already_completed": false
31+
}
32+
},
33+
{
34+
"id": "9712F2DF-DFC7-A3AA-66BC-191203654A1C",
35+
"type": "shop",
36+
"attributes": {
37+
"shop_id": "5712F2DF-DFC7-A3AA-66BC-191203654A1A",
38+
"queue_number": "A003",
39+
"state": "idled",
40+
"scan_state": "unscanned",
41+
"created_at": "2025-01-02T12:00:00.000Z",
42+
"shop_name": "8th & Townsend",
43+
"customer_read_at": "2025-01-02T12:00:01.000Z",
44+
"completed_at": "2025-01-02T12:00:03.000Z",
45+
"already_completed": false
46+
}
47+
}
48+
]
49+
}

app/src/main/kotlin/com/nativeapptemplate/nativeapptemplatefree/MainActivity.kt

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package com.nativeapptemplate.nativeapptemplatefree
22

3+
import android.content.Intent
34
import android.graphics.Color
5+
import android.nfc.NdefMessage
6+
import android.nfc.NfcAdapter
7+
import android.os.Build.VERSION.SDK_INT
48
import android.os.Bundle
59
import androidx.activity.ComponentActivity
610
import androidx.activity.SystemBarStyle
@@ -22,13 +26,16 @@ import com.nativeapptemplate.nativeapptemplatefree.MainActivityUiState.Success
2226
import com.nativeapptemplate.nativeapptemplatefree.data.login.LoginRepository
2327
import com.nativeapptemplate.nativeapptemplatefree.designsystem.theme.NatTheme
2428
import com.nativeapptemplate.nativeapptemplatefree.model.DarkThemeConfig
29+
import com.nativeapptemplate.nativeapptemplatefree.model.ItemTagInfoFromNdefMessage
2530
import com.nativeapptemplate.nativeapptemplatefree.ui.app_root.NatApp
2631
import com.nativeapptemplate.nativeapptemplatefree.ui.app_root.rememberNatAppState
2732
import com.nativeapptemplate.nativeapptemplatefree.utils.NetworkMonitor
33+
import com.nativeapptemplate.nativeapptemplatefree.utils.Utility
2834
import dagger.hilt.android.AndroidEntryPoint
2935
import kotlinx.coroutines.flow.collect
3036
import kotlinx.coroutines.flow.onEach
3137
import kotlinx.coroutines.launch
38+
import java.util.Date
3239
import javax.inject.Inject
3340

3441
@AndroidEntryPoint
@@ -47,6 +54,16 @@ class MainActivity : ComponentActivity() {
4754

4855
var uiState: MainActivityUiState by mutableStateOf(Loading)
4956

57+
viewModel.updateShouldNavigateToScanView(false)
58+
viewModel.updateShouldFetchItemTagForShowTagInfoScan(false)
59+
viewModel.updateShouldCompleteItemTagForCompleteScan(false)
60+
viewModel.initScanViewSelectedTabIndex()
61+
viewModel.initShowTagInfoScanResult()
62+
viewModel.initCompleteScanResult()
63+
64+
// viewModel.updateDidShowTapShopBelowTip(false)
65+
// viewModel.updateDidShowReadInstructionsTip(false)
66+
5067
// Update the uiState
5168
lifecycleScope.launch {
5269
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -103,12 +120,81 @@ class MainActivity : ComponentActivity() {
103120
NatApp(appState)
104121
}
105122
}
123+
124+
val intent = intent
125+
if (NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action) {
126+
viewModel.updateShouldNavigateToScanView(false)
127+
128+
val ndefMessage: NdefMessage?
129+
val rawMessages = if (SDK_INT >= 33) { // TIRAMISU
130+
intent.getParcelableArrayExtra(
131+
NfcAdapter.EXTRA_NDEF_MESSAGES,
132+
NdefMessage::class.java
133+
)
134+
}else{
135+
@Suppress("DEPRECATION")
136+
intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
137+
}
138+
139+
if (!rawMessages.isNullOrEmpty()) {
140+
ndefMessage = rawMessages[0] as NdefMessage
141+
142+
val itemTagInfoFromNdefMessage = Utility.extractItemTagInfoFrom(
143+
context = this,
144+
ndefMessage = ndefMessage
145+
)
146+
147+
updateItemTagInfoFromNdefMessage(itemTagInfoFromNdefMessage)
148+
viewModel.initScanViewSelectedTabIndex()
149+
viewModel.updateShouldNavigateToScanView(true)
150+
}
151+
}
152+
}
153+
154+
override fun onNewIntent(intent: Intent) {
155+
super.onNewIntent(intent)
156+
if (NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action) {
157+
viewModel.updateShouldNavigateToScanView(false)
158+
159+
val ndefMessage: NdefMessage?
160+
val rawMessages = if (SDK_INT >= 33) { // TIRAMISU
161+
intent.getParcelableArrayExtra(
162+
NfcAdapter.EXTRA_NDEF_MESSAGES,
163+
NdefMessage::class.java
164+
)
165+
}else{
166+
@Suppress("DEPRECATION")
167+
intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
168+
}
169+
170+
if (!rawMessages.isNullOrEmpty()) {
171+
ndefMessage = rawMessages[0] as NdefMessage
172+
173+
val itemTagInfoFromNdefMessage = Utility.extractItemTagInfoFrom(
174+
context = this,
175+
ndefMessage = ndefMessage
176+
)
177+
178+
updateItemTagInfoFromNdefMessage(itemTagInfoFromNdefMessage)
179+
viewModel.initScanViewSelectedTabIndex()
180+
viewModel.updateShouldNavigateToScanView(true)
181+
}
182+
}
106183
}
107184

108185
override fun onResume() {
109186
super.onResume()
110187
viewModel.updatePermissions()
111188
}
189+
190+
private fun updateItemTagInfoFromNdefMessage(itemTagInfoFromNdefMessage: ItemTagInfoFromNdefMessage) {
191+
if (itemTagInfoFromNdefMessage.success) {
192+
itemTagInfoFromNdefMessage.scannedAt = Date().toInstant().toString()
193+
}
194+
195+
viewModel.updateItemTagInfoFromNdefMessage(itemTagInfoFromNdefMessage)
196+
viewModel.updateShouldCompleteItemTagForCompleteScan(itemTagInfoFromNdefMessage.success)
197+
}
112198
}
113199

114200
/**

app/src/main/kotlin/com/nativeapptemplate/nativeapptemplatefree/MainActivityViewModel.kt

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ import androidx.lifecycle.viewModelScope
66
import com.nativeapptemplate.nativeapptemplatefree.MainActivityUiState.Loading
77
import com.nativeapptemplate.nativeapptemplatefree.MainActivityUiState.Success
88
import com.nativeapptemplate.nativeapptemplatefree.data.login.LoginRepository
9+
import com.nativeapptemplate.nativeapptemplatefree.model.CompleteScanResult
10+
import com.nativeapptemplate.nativeapptemplatefree.model.CompleteScanResultType
11+
import com.nativeapptemplate.nativeapptemplatefree.model.ItemTagInfoFromNdefMessage
912
import com.nativeapptemplate.nativeapptemplatefree.model.Permissions
13+
import com.nativeapptemplate.nativeapptemplatefree.model.ShowTagInfoScanResult
1014
import com.nativeapptemplate.nativeapptemplatefree.model.UserData
1115
import dagger.hilt.android.lifecycle.HiltViewModel
1216
import kotlinx.coroutines.flow.Flow
@@ -32,6 +36,54 @@ class MainActivityViewModel @Inject constructor(
3236
started = SharingStarted.WhileSubscribed(5_000),
3337
)
3438

39+
fun updateShouldFetchItemTagForShowTagInfoScan(shouldFetchItemTagForShowTagInfoScan: Boolean) {
40+
viewModelScope.launch {
41+
loginRepository.setShouldFetchItemTagForShowTagInfoScan(shouldFetchItemTagForShowTagInfoScan)
42+
}
43+
}
44+
45+
fun updateShouldCompleteItemTagForCompleteScan(shouldCompleteItemTagForCompleteScan: Boolean) {
46+
viewModelScope.launch {
47+
loginRepository.setShouldCompleteItemTagForCompleteScan(shouldCompleteItemTagForCompleteScan)
48+
}
49+
}
50+
51+
fun updateShouldNavigateToScanView(shouldNavigateToScanView: Boolean) {
52+
viewModelScope.launch {
53+
loginRepository.setShouldNavigateToScanView(shouldNavigateToScanView)
54+
}
55+
}
56+
57+
fun initScanViewSelectedTabIndex() {
58+
viewModelScope.launch {
59+
loginRepository.setScanViewSelectedTabIndex(0)
60+
}
61+
}
62+
63+
fun initShowTagInfoScanResult() {
64+
viewModelScope.launch {
65+
loginRepository.setShowTagInfoScanResult(ShowTagInfoScanResult())
66+
}
67+
}
68+
69+
fun initCompleteScanResult() {
70+
viewModelScope.launch {
71+
loginRepository.setCompleteScanResult(CompleteScanResult())
72+
}
73+
}
74+
75+
fun updateDidShowTapShopBelowTip(didShowTapShopBelowTip: Boolean) {
76+
viewModelScope.launch {
77+
loginRepository.setDidShowTapShopBelowTip(didShowTapShopBelowTip)
78+
}
79+
}
80+
81+
fun updateDidShowReadInstructionsTip(didShowReadInstructionsTip: Boolean) {
82+
viewModelScope.launch {
83+
loginRepository.setDidShowReadInstructionsTip(didShowReadInstructionsTip)
84+
}
85+
}
86+
3587
fun updatePermissions() {
3688
viewModelScope.launch {
3789
val isLoggedIn = loginRepository.isLoggedIn().first()
@@ -58,6 +110,30 @@ class MainActivityViewModel @Inject constructor(
58110
}
59111
}
60112

113+
fun updateItemTagInfoFromNdefMessage(
114+
itemTagInfoFromNdefMessage: ItemTagInfoFromNdefMessage,
115+
) {
116+
viewModelScope.launch {
117+
val completeScanResult = CompleteScanResult()
118+
completeScanResult.itemTagInfoFromNdefMessage = itemTagInfoFromNdefMessage
119+
120+
if (!completeScanResult.itemTagInfoFromNdefMessage.success) {
121+
completeScanResult.message = itemTagInfoFromNdefMessage.message
122+
completeScanResult.completeScanResultType = CompleteScanResultType.Failed
123+
}
124+
125+
try {
126+
loginRepository.setCompleteScanResult(completeScanResult)
127+
} catch (exception: Exception) {
128+
val message = exception.message
129+
completeScanResult.message = message ?: "Unknown Error"
130+
completeScanResult.completeScanResultType = CompleteScanResultType.Failed
131+
132+
loginRepository.setCompleteScanResult(completeScanResult)
133+
}
134+
}
135+
}
136+
61137
fun isLoggedIn(): StateFlow<Boolean> = loginRepository
62138
.isLoggedIn()
63139
.stateIn(

app/src/main/kotlin/com/nativeapptemplate/nativeapptemplatefree/NatConstants.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package com.nativeapptemplate.nativeapptemplatefree
22

33
object NatConstants {
4+
const val SCAN_PATH: String = "scan"
5+
const val SCAN_PATH_CUSTOMER: String = "scan_customer"
6+
47
const val SUPPORT_MAIL: String = "[email protected]"
58
const val HOW_TO_USE_URL: String = "https://myturntag.com/how"
69
const val SUPPORT_WEBSITE_URL: String = "https://nativeapptemplate.com"
10+
const val FAQS_URL: String = "https://nativeapptemplate.com/faqs"
711
const val DISCUSSIONS_URL: String = "https://github.com/nativeapptemplate/NativeAppTemplate-Free-Android/discussions"
812
const val PRIVACY_POLICY_URL: String = "https://nativeapptemplate.com/privacy"
913
const val TERMS_OF_USE_URL: String = "https://nativeapptemplate.com/terms"

0 commit comments

Comments
 (0)