Skip to content

Commit 3af1460

Browse files
authored
Feature/fe 1906 add the photo instructions gallery steps in the claiming (#657)
* Add the photo verification intro screen on claiming * Remove the UI from claiming flow prompting hte user to start SPV. * Create Gallery step in the claiming flow * Prepopulate photos if there are such * Create the first step in the claiming flows (#663) * Fix PR comments * Feature/fe 1908 start the upload of the photos after claiming is successful (#664) * Create the mechanism to start the uploading as soon as a success claiming takes place * Create missing unit tests * Minor fixes
1 parent 22eace2 commit 3af1460

64 files changed

Lines changed: 1754 additions & 432 deletions

File tree

Some content is hidden

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

app/src/main/java/com/weatherxm/data/Modules.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ import com.weatherxm.ui.claimdevice.helium.frequency.ClaimHeliumFrequencyViewMod
168168
import com.weatherxm.ui.claimdevice.helium.pair.ClaimHeliumPairViewModel
169169
import com.weatherxm.ui.claimdevice.helium.result.ClaimHeliumResultViewModel
170170
import com.weatherxm.ui.claimdevice.location.ClaimLocationViewModel
171+
import com.weatherxm.ui.claimdevice.photosgallery.ClaimPhotosGalleryViewModel
171172
import com.weatherxm.ui.claimdevice.pulse.ClaimPulseViewModel
172173
import com.weatherxm.ui.claimdevice.wifi.ClaimWifiViewModel
173174
import com.weatherxm.ui.connectwallet.ConnectWalletViewModel
@@ -732,6 +733,7 @@ private val viewmodels = module {
732733
viewModelOf(::ClaimHeliumResultViewModel)
733734
viewModelOf(::ClaimHeliumViewModel)
734735
viewModelOf(::ClaimLocationViewModel)
736+
viewModelOf(::ClaimPhotosGalleryViewModel)
735737
viewModelOf(::ClaimPulseViewModel)
736738
viewModelOf(::ClaimWifiViewModel)
737739
viewModelOf(::ConnectWalletViewModel)

app/src/main/java/com/weatherxm/ui/Navigator.kt

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,7 @@ import com.weatherxm.ui.common.Contracts.ARG_DEVICE_TYPE
4545
import com.weatherxm.ui.common.Contracts.ARG_EXPLORER_CELL
4646
import com.weatherxm.ui.common.Contracts.ARG_FORECAST_SELECTED_DAY
4747
import com.weatherxm.ui.common.Contracts.ARG_INSTRUCTIONS_ONLY
48-
import com.weatherxm.ui.common.Contracts.ARG_NEEDS_PHOTO_VERIFICATION
4948
import com.weatherxm.ui.common.Contracts.ARG_NETWORK_STATS
50-
import com.weatherxm.ui.common.Contracts.ARG_NEW_PHOTO_VERIFICATION
5149
import com.weatherxm.ui.common.Contracts.ARG_OPEN_EXPLORER_ON_BACK
5250
import com.weatherxm.ui.common.Contracts.ARG_PHOTOS
5351
import com.weatherxm.ui.common.Contracts.ARG_REMOTE_MESSAGE
@@ -381,15 +379,13 @@ class Navigator(private val analytics: AnalyticsWrapper) {
381379
fun showDeviceHeliumOTA(
382380
context: Context?,
383381
device: UIDevice?,
384-
deviceIsBleConnected: Boolean = false,
385-
needsPhotoVerification: Boolean = false
382+
deviceIsBleConnected: Boolean = false
386383
) {
387384
context?.startActivity(
388385
Intent(context, DeviceHeliumOTAActivity::class.java)
389386
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
390387
.putExtra(ARG_DEVICE, device)
391388
.putExtra(ARG_BLE_DEVICE_CONNECTED, deviceIsBleConnected)
392-
.putExtra(ARG_NEEDS_PHOTO_VERIFICATION, needsPhotoVerification)
393389
)
394390
}
395391

@@ -516,14 +512,12 @@ class Navigator(private val analytics: AnalyticsWrapper) {
516512
activityResultLauncher: ActivityResultLauncher<Intent>?,
517513
context: Context,
518514
device: UIDevice,
519-
photos: ArrayList<String>,
520-
newPhotoVerification: Boolean
515+
photos: ArrayList<String>
521516
) {
522517
val intent = Intent(context, PhotoGalleryActivity::class.java)
523518
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
524519
.putExtra(ARG_DEVICE, device)
525520
.putStringArrayListExtra(ARG_PHOTOS, photos)
526-
.putExtra(ARG_NEW_PHOTO_VERIFICATION, newPhotoVerification)
527521
if (activityResultLauncher == null) {
528522
context.startActivity(intent)
529523
} else {
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package com.weatherxm.ui.claimdevice.beforeyouclaim
2+
3+
import android.os.Bundle
4+
import android.view.LayoutInflater
5+
import android.view.View
6+
import android.view.ViewGroup
7+
import androidx.compose.foundation.layout.Arrangement.spacedBy
8+
import androidx.compose.foundation.layout.Column
9+
import androidx.compose.ui.res.dimensionResource
10+
import com.weatherxm.R
11+
import com.weatherxm.databinding.FragmentClaimBeforeYouClaimBinding
12+
import com.weatherxm.ui.claimdevice.helium.ClaimHeliumViewModel
13+
import com.weatherxm.ui.claimdevice.pulse.ClaimPulseViewModel
14+
import com.weatherxm.ui.claimdevice.wifi.ClaimWifiViewModel
15+
import com.weatherxm.ui.common.Contracts.ARG_DEVICE_TYPE
16+
import com.weatherxm.ui.common.DeviceType
17+
import com.weatherxm.ui.common.parcelable
18+
import com.weatherxm.ui.components.BaseFragment
19+
import com.weatherxm.ui.components.compose.TextWithStartingIcon
20+
import org.koin.androidx.viewmodel.ext.android.activityViewModel
21+
22+
class ClaimBeforeYouClaimFragment : BaseFragment() {
23+
private val heliumParentModel: ClaimHeliumViewModel by activityViewModel()
24+
private val wifiParentModel: ClaimWifiViewModel by activityViewModel()
25+
private val pulseParentModel: ClaimPulseViewModel by activityViewModel()
26+
private lateinit var binding: FragmentClaimBeforeYouClaimBinding
27+
28+
companion object {
29+
fun newInstance(deviceType: DeviceType) = ClaimBeforeYouClaimFragment().apply {
30+
arguments = Bundle().apply { putParcelable(ARG_DEVICE_TYPE, deviceType) }
31+
}
32+
}
33+
34+
override fun onCreateView(
35+
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
36+
): View {
37+
binding = FragmentClaimBeforeYouClaimBinding.inflate(inflater, container, false)
38+
return binding.root
39+
}
40+
41+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
42+
super.onViewCreated(view, savedInstanceState)
43+
44+
val deviceType = arguments?.parcelable<DeviceType>(ARG_DEVICE_TYPE)
45+
if (context == null || deviceType == null) {
46+
// No point executing if in the meanwhile the activity is dead
47+
return
48+
}
49+
50+
binding.previousStepsView.setContent {
51+
Column(
52+
verticalArrangement = spacedBy(dimensionResource(R.dimen.margin_small_to_normal))
53+
) {
54+
TextWithStartingIcon(text = getString(R.string.check_box_contents))
55+
TextWithStartingIcon(
56+
text = getString(R.string.assemble_weather_station),
57+
iconRes = R.drawable.ic_two_filled
58+
)
59+
if (deviceType != DeviceType.HELIUM) {
60+
TextWithStartingIcon(
61+
text = getString(R.string.install_weather_station_following_guidelines),
62+
iconRes = R.drawable.ic_three_filled
63+
)
64+
}
65+
}
66+
}
67+
68+
binding.nextStepsView.setContent {
69+
Column(
70+
verticalArrangement = spacedBy(dimensionResource(R.dimen.margin_small_to_normal))
71+
) {
72+
if (deviceType == DeviceType.HELIUM) {
73+
TextWithStartingIcon(
74+
text = getString(R.string.pair_station_via_bluetooth),
75+
iconRes = R.drawable.ic_three_filled
76+
)
77+
TextWithStartingIcon(
78+
text = getString(R.string.install_weather_station_following_guidelines),
79+
iconRes = R.drawable.ic_four_filled
80+
)
81+
} else {
82+
TextWithStartingIcon(
83+
text = getString(R.string.connect_gateway_ready_for_claiming),
84+
iconRes = R.drawable.ic_four_filled
85+
)
86+
}
87+
TextWithStartingIcon(
88+
text = getString(R.string.confirm_station_exact_deployment_location),
89+
iconRes = R.drawable.ic_five_filled
90+
)
91+
TextWithStartingIcon(
92+
text = getString(R.string.take_photos_station_deployment_guidelines),
93+
iconRes = R.drawable.ic_six_filled
94+
)
95+
if (deviceType == DeviceType.HELIUM) {
96+
TextWithStartingIcon(
97+
text = getString(R.string.set_your_station_frequency),
98+
iconRes = R.drawable.ic_seven_filled
99+
)
100+
TextWithStartingIcon(
101+
text = getString(R.string.all_done_enjoy_station_earn_rewards),
102+
iconRes = R.drawable.ic_eight_filled
103+
)
104+
} else {
105+
TextWithStartingIcon(
106+
text = getString(R.string.all_done_enjoy_station_earn_rewards),
107+
iconRes = R.drawable.ic_seven_filled
108+
)
109+
}
110+
}
111+
}
112+
113+
binding.beginStationClaimingBtn.setOnClickListener {
114+
when (deviceType) {
115+
DeviceType.M5_WIFI, DeviceType.D1_WIFI -> wifiParentModel.next()
116+
DeviceType.PULSE_4G -> pulseParentModel.next()
117+
DeviceType.HELIUM -> heliumParentModel.next()
118+
}
119+
}
120+
}
121+
}

app/src/main/java/com/weatherxm/ui/claimdevice/helium/ClaimHeliumActivity.kt

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ import androidx.viewpager2.adapter.FragmentStateAdapter
88
import com.weatherxm.R
99
import com.weatherxm.analytics.AnalyticsService
1010
import com.weatherxm.databinding.ActivityClaimDeviceBinding
11+
import com.weatherxm.service.GlobalUploadObserverService
12+
import com.weatherxm.ui.claimdevice.beforeyouclaim.ClaimBeforeYouClaimFragment
1113
import com.weatherxm.ui.claimdevice.helium.ClaimHeliumActivity.ClaimHeliumDevicePagerAdapter.Companion.PAGE_COUNT
14+
import com.weatherxm.ui.claimdevice.helium.ClaimHeliumActivity.ClaimHeliumDevicePagerAdapter.Companion.PAGE_FREQUENCY
15+
import com.weatherxm.ui.claimdevice.helium.ClaimHeliumActivity.ClaimHeliumDevicePagerAdapter.Companion.PAGE_LOCATION
16+
import com.weatherxm.ui.claimdevice.helium.ClaimHeliumActivity.ClaimHeliumDevicePagerAdapter.Companion.PAGE_PHOTOS_GALLERY
17+
import com.weatherxm.ui.claimdevice.helium.ClaimHeliumActivity.ClaimHeliumDevicePagerAdapter.Companion.PAGE_RESULT
1218
import com.weatherxm.ui.claimdevice.helium.frequency.ClaimHeliumFrequencyFragment
1319
import com.weatherxm.ui.claimdevice.helium.frequency.ClaimHeliumFrequencyViewModel
1420
import com.weatherxm.ui.claimdevice.helium.pair.ClaimHeliumPairFragment
@@ -18,13 +24,18 @@ import com.weatherxm.ui.claimdevice.helium.result.ClaimHeliumResultFragment
1824
import com.weatherxm.ui.claimdevice.helium.result.ClaimHeliumResultViewModel
1925
import com.weatherxm.ui.claimdevice.location.ClaimLocationFragment
2026
import com.weatherxm.ui.claimdevice.location.ClaimLocationViewModel
27+
import com.weatherxm.ui.claimdevice.photosgallery.ClaimPhotosGalleryFragment
28+
import com.weatherxm.ui.claimdevice.photosgallery.ClaimPhotosGalleryViewModel
29+
import com.weatherxm.ui.claimdevice.photosintro.ClaimPhotosIntroFragment
2130
import com.weatherxm.ui.common.DeviceType
2231
import com.weatherxm.ui.common.classSimpleName
2332
import com.weatherxm.ui.common.empty
2433
import com.weatherxm.ui.common.parcelable
2534
import com.weatherxm.ui.common.visible
2635
import com.weatherxm.ui.components.BaseActivity
36+
import org.koin.android.ext.android.inject
2737
import org.koin.androidx.viewmodel.ext.android.viewModel
38+
import kotlin.getValue
2839

2940
class ClaimHeliumActivity : BaseActivity() {
3041
companion object {
@@ -34,11 +45,13 @@ class ClaimHeliumActivity : BaseActivity() {
3445
const val CLAIMED_DEVICE = "claimed_device"
3546
}
3647

48+
private val uploadObserverService: GlobalUploadObserverService by inject()
3749
private val model: ClaimHeliumViewModel by viewModel()
3850
private val locationModel: ClaimLocationViewModel by viewModel()
3951
private val frequencyModel: ClaimHeliumFrequencyViewModel by viewModel()
4052
private val resultModel: ClaimHeliumResultViewModel by viewModel()
4153
private val pairModel: ClaimHeliumPairViewModel by viewModel()
54+
private val photosViewModel: ClaimPhotosGalleryViewModel by viewModel()
4255
private lateinit var binding: ActivityClaimDeviceBinding
4356

4457
override fun onCreate(savedInstanceState: Bundle?) {
@@ -69,6 +82,20 @@ class ClaimHeliumActivity : BaseActivity() {
6982
if (it) onNextPressed()
7083
}
7184

85+
model.onPhotosMetadata().observe(this) { (device, metadata) ->
86+
val numberOfPhotosToUpload = List(photosViewModel.onPhotos.size) { index ->
87+
metadata.getOrNull(index)
88+
}.filterNotNull().size
89+
uploadObserverService.setData(device, numberOfPhotosToUpload)
90+
91+
startWorkerForUploadingPhotos(
92+
device,
93+
photosViewModel.onPhotos.toList(),
94+
metadata,
95+
DeviceType.HELIUM.name
96+
)
97+
}
98+
7299
savedInstanceState?.let {
73100
binding.pager.currentItem = it.getInt(CURRENT_PAGE, 0)
74101
binding.progress.progress = binding.pager.currentItem + 1
@@ -95,13 +122,12 @@ class ClaimHeliumActivity : BaseActivity() {
95122
binding.progress.progress = binding.pager.currentItem + 1
96123

97124
when (pager.currentItem) {
98-
ClaimHeliumDevicePagerAdapter.PAGE_LOCATION -> {
99-
locationModel.requestUserLocation()
100-
}
101-
ClaimHeliumDevicePagerAdapter.PAGE_FREQUENCY -> {
125+
PAGE_LOCATION -> locationModel.requestUserLocation()
126+
PAGE_PHOTOS_GALLERY -> photosViewModel.requestCameraPermission()
127+
PAGE_FREQUENCY -> {
102128
frequencyModel.getCountryAndFrequencies(locationModel.getInstallationLocation())
103129
}
104-
ClaimHeliumDevicePagerAdapter.PAGE_RESULT -> {
130+
PAGE_RESULT -> {
105131
binding.appBar.visible(false)
106132
binding.progress.visible(false)
107133
resultModel.setSelectedDevice(pairModel.getSelectedDevice())
@@ -125,22 +151,28 @@ class ClaimHeliumActivity : BaseActivity() {
125151
activity: AppCompatActivity,
126152
) : FragmentStateAdapter(activity) {
127153
companion object {
128-
const val PAGE_RESET = 0
129-
const val PAGE_VERIFY_OR_PAIR = 1
130-
const val PAGE_LOCATION = 2
131-
const val PAGE_FREQUENCY = 3
132-
const val PAGE_RESULT = 4
133-
const val PAGE_COUNT = 5
154+
const val PAGE_BEFORE_CLAIMING = 0
155+
const val PAGE_RESET = 1
156+
const val PAGE_VERIFY_OR_PAIR = 2
157+
const val PAGE_LOCATION = 3
158+
const val PAGE_PHOTOS_INTRO = 4
159+
const val PAGE_PHOTOS_GALLERY = 5
160+
const val PAGE_FREQUENCY = 6
161+
const val PAGE_RESULT = 7
162+
const val PAGE_COUNT = 8
134163
}
135164

136165
override fun getItemCount(): Int = PAGE_COUNT
137166

138167
@Suppress("UseCheckOrError")
139168
override fun createFragment(position: Int): Fragment {
140169
return when (position) {
170+
PAGE_BEFORE_CLAIMING -> ClaimBeforeYouClaimFragment.newInstance(DeviceType.HELIUM)
141171
PAGE_RESET -> ClaimHeliumResetFragment()
142172
PAGE_VERIFY_OR_PAIR -> ClaimHeliumPairFragment()
143173
PAGE_LOCATION -> ClaimLocationFragment.newInstance(DeviceType.HELIUM)
174+
PAGE_PHOTOS_INTRO -> ClaimPhotosIntroFragment.newInstance(DeviceType.HELIUM)
175+
PAGE_PHOTOS_GALLERY -> ClaimPhotosGalleryFragment.newInstance(DeviceType.HELIUM)
144176
PAGE_FREQUENCY -> ClaimHeliumFrequencyFragment()
145177
PAGE_RESULT -> ClaimHeliumResultFragment()
146178
else -> throw IllegalStateException("Oops! You forgot to add a fragment here.")

app/src/main/java/com/weatherxm/ui/claimdevice/helium/ClaimHeliumViewModel.kt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.weatherxm.ui.claimdevice.helium
22

3+
import androidx.lifecycle.LiveData
34
import androidx.lifecycle.MutableLiveData
45
import androidx.lifecycle.ViewModel
56
import androidx.lifecycle.viewModelScope
@@ -12,10 +13,13 @@ import com.weatherxm.data.models.ApiError.UserError.ClaimError.InvalidClaimId
1213
import com.weatherxm.data.models.ApiError.UserError.ClaimError.InvalidClaimLocation
1314
import com.weatherxm.data.models.Frequency
1415
import com.weatherxm.data.models.Location
16+
import com.weatherxm.data.models.PhotoPresignedMetadata
1517
import com.weatherxm.ui.common.Resource
18+
import com.weatherxm.ui.common.StationPhoto
1619
import com.weatherxm.ui.common.UIDevice
1720
import com.weatherxm.ui.common.empty
1821
import com.weatherxm.usecases.ClaimDeviceUseCase
22+
import com.weatherxm.usecases.DevicePhotoUseCase
1923
import com.weatherxm.util.Failure.getDefaultMessageResId
2024
import com.weatherxm.util.Resources
2125
import kotlinx.coroutines.CoroutineDispatcher
@@ -26,17 +30,21 @@ import timber.log.Timber
2630
@Suppress("TooManyFunctions")
2731
class ClaimHeliumViewModel(
2832
private val claimDeviceUseCase: ClaimDeviceUseCase,
33+
private val photoUseCase: DevicePhotoUseCase,
2934
private val resources: Resources,
3035
private val analytics: AnalyticsWrapper,
3136
private val dispatcher: CoroutineDispatcher,
3237
) : ViewModel() {
3338
private val onCancel = MutableLiveData(false)
3439
private val onNext = MutableLiveData(false)
3540
private val onClaimResult = MutableLiveData<Resource<UIDevice>>()
41+
private val onPhotosMetadata = MutableLiveData<Pair<UIDevice, List<PhotoPresignedMetadata>>>()
3642

3743
fun onCancel() = onCancel
3844
fun onNext() = onNext
3945
fun onClaimResult() = onClaimResult
46+
fun onPhotosMetadata(): LiveData<Pair<UIDevice, List<PhotoPresignedMetadata>>> =
47+
onPhotosMetadata
4048

4149
private var devEUI: String = String.empty()
4250
private var deviceKey: String = String.empty()
@@ -74,11 +82,12 @@ class ClaimHeliumViewModel(
7482
onNext.postValue(true)
7583
}
7684

77-
fun claimDevice(location: Location) {
85+
fun claimDevice(location: Location, photos: List<StationPhoto>) {
7886
onClaimResult.postValue(Resource.loading())
7987
viewModelScope.launch(dispatcher) {
8088
claimDeviceUseCase.claimDevice(devEUI, location.lat, location.lon, deviceKey).onRight {
8189
Timber.d("Claimed device: $it")
90+
prepareUpload(it, photos)
8291
onClaimResult.postValue(Resource.success(it))
8392
}.onLeft {
8493
analytics.trackEventFailure(it.code)
@@ -100,6 +109,13 @@ class ClaimHeliumViewModel(
100109
}
101110
}
102111

112+
suspend fun prepareUpload(device: UIDevice, photos: List<StationPhoto>) {
113+
photoUseCase.getPhotosMetadataForUpload(device.id, photos.mapNotNull { it.localPath })
114+
.onRight {
115+
onPhotosMetadata.postValue(Pair(device, it))
116+
}
117+
}
118+
103119
fun setClaimedDevice(device: UIDevice?) {
104120
device?.let { onClaimResult.postValue(Resource.success(device)) }
105121
}

0 commit comments

Comments
 (0)