Skip to content

Commit 4f22523

Browse files
committed
Generating correct QR code and join from QR code now functional
1 parent a74a2f8 commit 4f22523

25 files changed

+426
-337
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ openarchive-release.keystore
1212
*.class
1313

1414
# Generated files
15+
app/build/
16+
app/debug/
17+
app/release/
1518
bin/
1619
gen/
17-
app/build/
1820
release/
1921
spoon-output/
2022
screenshots/

app/src/androidTest/java/net/opendasharchive/openarchive/features/main/StartTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ class StartTest {
3333

3434
@Test
3535
fun testCreateRepo() = runTest {
36-
val result = createRepo("blrSIdKPpLPlfJI6M9bTQFjW9BlnwboPzLQ-GPlJsGw", "r1")
36+
val result = parseSnowbirdRepoResponse("blrSIdKPpLPlfJI6M9bTQFjW9BlnwboPzLQ-GPlJsGw", "r1")
3737
assertEquals(ApiResponse.SingleResponse(repo), result)
3838
}
3939

4040
@Suppress("SameParameterValue")
41-
private fun createRepo(groupKey: String, repoName: String): ApiResponse<SnowbirdRepo> {
41+
private fun parseSnowbirdRepoResponse(groupKey: String, repoName: String): ApiResponse<SnowbirdRepo> {
4242
val client = UnixSocketClient()
4343
val json = Json { ignoreUnknownKeys = true }
4444
val jsonString = "{ \"key\":\"blrSIdKPpLPlfJI6M9bTQFjW9BlnwboPzLQ-GPlJsGw\", \"name\":\"r1\" }"

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@
108108
<category android:name="android.intent.category.DEFAULT" />
109109
<category android:name="android.intent.category.BROWSABLE" />
110110
<data
111-
android:scheme="save-veilid" />
111+
android:scheme="save+dweb" />
112112
</intent-filter>
113113
</activity>
114114
<activity

app/src/main/java/net/opendasharchive/openarchive/db/RequestNameDTO.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,7 @@ package net.opendasharchive.openarchive.db
33
import kotlinx.serialization.Serializable
44

55
@Serializable
6-
data class RequestName(val name: String): SerializableMarker
6+
data class RequestName(val name: String): SerializableMarker
7+
8+
@Serializable
9+
data class MembershipRequest(val uri: String): SerializableMarker
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package net.opendasharchive.openarchive.db
2+
3+
import kotlinx.serialization.Serializable
4+
5+
@Serializable
6+
data class SnowbirdMediaList(
7+
var media: List<SnowbirdMediaItem>
8+
) : SerializableMarker
9+
10+
@Serializable
11+
data class SnowbirdMediaItem(
12+
val uri: String,
13+
val aspectRatio: Float
14+
): SerializableMarker

app/src/main/java/net/opendasharchive/openarchive/features/main/UnixSocketClient.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class UnixSocketClient(private val socketPath: String = "/data/user/0/net.openda
8181
}
8282
}
8383
} catch (e: Exception) {
84-
ClientResponse.ErrorResponse(ApiError.UnexpectedError("Unexpected error: ${e.localizedMessage}"))
84+
ClientResponse.ErrorResponse(ApiError.UnexpectedError(e.localizedMessage ?: "Unknown error"))
8585
}
8686
}
8787

@@ -107,7 +107,7 @@ class UnixSocketClient(private val socketPath: String = "/data/user/0/net.openda
107107
}
108108
}
109109
} catch (e: Exception) {
110-
ClientResponse.ErrorResponse(ApiError.UnexpectedError("Unexpected error: ${e.localizedMessage}"))
110+
ClientResponse.ErrorResponse(ApiError.UnexpectedError(e.localizedMessage ?: "Unknown error"))
111111
}
112112
}
113113

app/src/main/java/net/opendasharchive/openarchive/services/snowbird/BaseSnowbirdFragment.kt

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,32 @@
11
package net.opendasharchive.openarchive.services.snowbird
22

3+
import android.content.Context
4+
import android.view.View
5+
import android.view.inputmethod.InputMethodManager
36
import androidx.fragment.app.Fragment
7+
import net.opendasharchive.openarchive.db.SnowbirdError
48
import net.opendasharchive.openarchive.util.FullScreenOverlayManager
9+
import net.opendasharchive.openarchive.util.Utility
510
import org.koin.androidx.viewmodel.ext.android.viewModel
611

712
open class BaseSnowbirdFragment : Fragment() {
813
val snowbirdGroupViewModel: SnowbirdGroupViewModel by viewModel()
914
val snowbirdRepoViewModel: SnowbirdRepoViewModel by viewModel()
10-
val snowbirdMediaViewModel: SnowbirdMediaViewModel by viewModel()
1115

12-
fun handleProcessingStatus(isProcessing: Boolean) {
13-
if (isProcessing) {
16+
open fun dismissKeyboard(view: View) {
17+
val imm = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
18+
imm.hideSoftInputFromWindow(view.windowToken, 0)
19+
}
20+
21+
open fun handleError(error: SnowbirdError) {
22+
Utility.showMaterialWarning(
23+
requireContext(),
24+
error.friendlyMessage
25+
)
26+
}
27+
28+
open fun handleLoadingStatus(isLoading: Boolean) {
29+
if (isLoading) {
1430
FullScreenOverlayManager.show(this@BaseSnowbirdFragment)
1531
} else {
1632
FullScreenOverlayManager.hide()

app/src/main/java/net/opendasharchive/openarchive/services/snowbird/SnowbirdAPI.kt

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,23 @@ package net.opendasharchive.openarchive.services.snowbird
22

33
import net.opendasharchive.openarchive.db.EmptyRequest
44
import net.opendasharchive.openarchive.db.Media
5+
import net.opendasharchive.openarchive.db.MembershipRequest
56
import net.opendasharchive.openarchive.db.RequestName
67
import net.opendasharchive.openarchive.db.SnowbirdGroup
78
import net.opendasharchive.openarchive.db.SnowbirdGroupList
8-
import net.opendasharchive.openarchive.db.SnowbirdMediaDTO
9+
import net.opendasharchive.openarchive.db.SnowbirdMediaItem
10+
import net.opendasharchive.openarchive.db.SnowbirdMediaList
911
import net.opendasharchive.openarchive.db.SnowbirdRepo
1012
import net.opendasharchive.openarchive.db.SnowbirdRepoList
1113
import net.opendasharchive.openarchive.features.main.ClientResponse
1214
import net.opendasharchive.openarchive.features.main.HttpMethod
1315
import net.opendasharchive.openarchive.features.main.UnixSocketClient
1416

1517
interface ISnowbirdAPI {
16-
// Files
17-
suspend fun downloadMedia(groupKey: String, repoKey: String, mediaKey: String): ApiResponse<SnowbirdMediaDTO>
18-
suspend fun uploadMedia(groupKey: String, repoKey: String, media: Media): ApiResponse<MediaID>
18+
// Media
19+
suspend fun fetchMedia(groupKey: String, repoKey: String): ApiResponse<SnowbirdMediaItem>
20+
suspend fun downloadMedia(groupKey: String, repoKey: String, mediaKey: String): ApiResponse<SnowbirdMediaItem>
21+
suspend fun uploadMedia(groupKey: String, repoKey: String, media: Media): ApiResponse<SnowbirdMediaItem>
1922

2023
// Groups
2124
suspend fun createGroup(groupName: String): ApiResponse<SnowbirdGroup>
@@ -30,24 +33,34 @@ interface ISnowbirdAPI {
3033
class SnowbirdAPI(private var client: UnixSocketClient): ISnowbirdAPI {
3134

3235
companion object {
33-
const val BASE_PATH = "/api"
36+
private const val BASE_PATH = "/api"
37+
const val MEMBERSHIPS_PATH = "$BASE_PATH/memberships"
3438
const val GROUPS_PATH = "$BASE_PATH/groups"
3539
const val REPOS_PATH = "$BASE_PATH/groups/%s/repos"
3640
const val MEDIA_PATH = "$BASE_PATH/groups/%s/repos/%s/media"
3741
const val MEDIA_PATH_UPLOAD = "$BASE_PATH/groups/%s/repos/%s/media/%s"
3842
}
3943

40-
override suspend fun downloadMedia(groupKey: String, repoKey: String, mediaKey: String): ApiResponse<SnowbirdMediaDTO> {
41-
return when (val response = client.sendRequest<EmptyRequest, SnowbirdMediaDTO>(
44+
override suspend fun downloadMedia(groupKey: String, repoKey: String, mediaKey: String): ApiResponse<SnowbirdMediaItem> {
45+
return when (val response = client.sendRequest<EmptyRequest, SnowbirdMediaItem>(
4246
MEDIA_PATH_UPLOAD.format(groupKey, repoKey, mediaKey), HttpMethod.GET)
4347
) {
4448
is ClientResponse.SuccessResponse -> ApiResponse.SingleResponse(response.data)
4549
is ClientResponse.ErrorResponse -> ApiResponse.ErrorResponse(response.error)
4650
}
4751
}
4852

49-
override suspend fun uploadMedia(groupKey: String, repoKey: String, media: Media): ApiResponse<MediaID> {
50-
return when (val response = client.sendBinaryRequest<MediaID>(
53+
override suspend fun fetchMedia(groupKey: String, repoKey: String): ApiResponse<SnowbirdMediaItem> {
54+
return when (val response = client.sendRequest<RequestName, SnowbirdMediaList>(
55+
endpoint = MEDIA_PATH.format(groupKey, repoKey), HttpMethod.GET)
56+
) {
57+
is ClientResponse.SuccessResponse -> ApiResponse.ListResponse(response.data.media)
58+
is ClientResponse.ErrorResponse -> ApiResponse.ErrorResponse(response.error)
59+
}
60+
}
61+
62+
override suspend fun uploadMedia(groupKey: String, repoKey: String, media: Media): ApiResponse<SnowbirdMediaItem> {
63+
return when (val response = client.sendBinaryRequest<SnowbirdMediaItem>(
5164
endpoint = MEDIA_PATH.format(groupKey, repoKey), HttpMethod.GET, media.file.inputStream())
5265
) {
5366
is ClientResponse.SuccessResponse -> ApiResponse.SingleResponse(response.data)
@@ -76,9 +89,8 @@ class SnowbirdAPI(private var client: UnixSocketClient): ISnowbirdAPI {
7689
}
7790
}
7891

79-
// TODO: This is not functional yet.
8092
override suspend fun joinGroup(uriString: String): ApiResponse<SnowbirdGroup> {
81-
return when (val response = client.sendRequest<EmptyRequest, SnowbirdGroup>("$GROUPS_PATH/$uriString", HttpMethod.GET)) {
93+
return when (val response = client.sendRequest<MembershipRequest, SnowbirdGroup>(MEMBERSHIPS_PATH, HttpMethod.POST, MembershipRequest(uriString))) {
8294
is ClientResponse.SuccessResponse -> ApiResponse.SingleResponse(response.data)
8395
is ClientResponse.ErrorResponse -> ApiResponse.ErrorResponse(response.error)
8496
}

app/src/main/java/net/opendasharchive/openarchive/services/snowbird/SnowbirdCreateGroupFragment.kt

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
package net.opendasharchive.openarchive.services.snowbird
22

3-
import android.content.Context
43
import android.os.Bundle
54
import android.view.LayoutInflater
65
import android.view.View
76
import android.view.ViewGroup
8-
import android.view.inputmethod.InputMethodManager
97
import androidx.lifecycle.Lifecycle
108
import androidx.lifecycle.lifecycleScope
119
import androidx.lifecycle.repeatOnLifecycle
@@ -14,10 +12,7 @@ import kotlinx.coroutines.launch
1412
import net.opendasharchive.openarchive.databinding.FragmentSnowbirdCreateGroupBinding
1513
import net.opendasharchive.openarchive.db.SnowbirdGroup
1614
import net.opendasharchive.openarchive.db.SnowbirdRepo
17-
import net.opendasharchive.openarchive.extensions.collectLifecycleFlow
18-
import net.opendasharchive.openarchive.util.FullScreenOverlayManager
1915
import net.opendasharchive.openarchive.util.Utility
20-
import timber.log.Timber
2116

2217
class SnowbirdCreateGroupFragment : BaseSnowbirdFragment() {
2318

@@ -37,44 +32,58 @@ class SnowbirdCreateGroupFragment : BaseSnowbirdFragment() {
3732
dismissKeyboard(it)
3833
}
3934

40-
viewLifecycleOwner.collectLifecycleFlow(snowbirdGroupViewModel.group) { group ->
41-
group?.let { handleGroupCreated(it) }
42-
}
43-
44-
viewLifecycleOwner.collectLifecycleFlow(snowbirdRepoViewModel.repo) { repo ->
45-
repo?.let { handleRepoCreated(it) }
46-
}
35+
initializeViewModelObservers()
36+
}
4737

38+
private fun initializeViewModelObservers() {
4839
lifecycleScope.launch {
49-
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
50-
snowbirdGroupViewModel.isProcessing.collect { isProcessing ->
51-
Timber.d("is processing? $isProcessing")
52-
if (isProcessing) {
53-
FullScreenOverlayManager.show(this@SnowbirdCreateGroupFragment)
54-
} else {
55-
FullScreenOverlayManager.hide()
56-
}
57-
}
40+
repeatOnLifecycle(Lifecycle.State.STARTED) {
41+
launch { snowbirdGroupViewModel.isProcessing.collect { isProcessing -> handleLoadingStatus(isProcessing) } }
42+
launch { snowbirdGroupViewModel.groupState.collect { state -> handleGroupStateUpdate(state) } }
43+
launch { snowbirdRepoViewModel.repoState.collect { state -> handleRepoStateUpdate(state) } }
5844
}
5945
}
6046
}
6147

62-
private fun dismissKeyboard(view: View) {
63-
val imm = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
64-
imm.hideSoftInputFromWindow(view.windowToken, 0)
48+
private fun handleGroupStateUpdate(state: SnowbirdGroupViewModel.GroupState) {
49+
when (state) {
50+
is SnowbirdGroupViewModel.GroupState.Idle -> { /* Initial state */ }
51+
is SnowbirdGroupViewModel.GroupState.Loading -> handleLoadingStatus(true)
52+
is SnowbirdGroupViewModel.GroupState.SingleGroupSuccess -> handleGroupCreated(state.group)
53+
is SnowbirdGroupViewModel.GroupState.Error -> handleError(state.error)
54+
else -> Unit
55+
}
56+
}
57+
58+
private fun handleRepoStateUpdate(state: SnowbirdRepoViewModel.RepoState) {
59+
when (state) {
60+
is SnowbirdRepoViewModel.RepoState.Idle -> { /* Initial state */ }
61+
is SnowbirdRepoViewModel.RepoState.Loading -> handleLoadingStatus(true)
62+
is SnowbirdRepoViewModel.RepoState.SingleRepoSuccess -> handleRepoCreated(state.repo)
63+
is SnowbirdRepoViewModel.RepoState.Error -> handleError(state.error)
64+
else -> Unit
65+
}
6566
}
6667

67-
private suspend fun handleGroupCreated(group: SnowbirdGroup) {
68-
group.save()
69-
snowbirdRepoViewModel.createRepo(
70-
group.key,
71-
viewBinding.repoNameTextfield.text.toString())
68+
private fun handleGroupCreated(group: SnowbirdGroup?) {
69+
group?.let {
70+
snowbirdGroupViewModel.setCurrentGroup(group)
71+
72+
lifecycleScope.launch {
73+
group.save()
74+
snowbirdRepoViewModel.createRepo(
75+
group.key, viewBinding.repoNameTextfield.text.toString()
76+
)
77+
}
78+
}
7279
}
7380

74-
private fun handleRepoCreated(repo: SnowbirdRepo) {
75-
repo.snowbirdGroup = snowbirdGroupViewModel.group.value
76-
repo.save()
77-
showConfirmation(repo)
81+
private fun handleRepoCreated(repo: SnowbirdRepo?) {
82+
repo?.let {
83+
repo.snowbirdGroup = snowbirdGroupViewModel.currentGroup.value
84+
repo.save()
85+
showConfirmation(repo)
86+
}
7887
}
7988

8089
private fun showConfirmation(repo: SnowbirdRepo?) {

app/src/main/java/net/opendasharchive/openarchive/services/snowbird/SnowbirdFragment.kt

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,16 @@ import android.os.Bundle
44
import android.view.LayoutInflater
55
import android.view.View
66
import android.view.ViewGroup
7-
import android.widget.Toast
87
import androidx.activity.result.contract.ActivityResultContracts
8+
import androidx.lifecycle.Lifecycle
99
import androidx.lifecycle.lifecycleScope
10+
import androidx.lifecycle.repeatOnLifecycle
1011
import androidx.navigation.fragment.findNavController
1112
import com.google.zxing.integration.android.IntentIntegrator
1213
import kotlinx.coroutines.launch
1314
import net.opendasharchive.openarchive.databinding.FragmentSnowbirdBinding
14-
import net.opendasharchive.openarchive.db.SnowbirdError
15-
import net.opendasharchive.openarchive.extensions.collectLifecycleFlow
1615
import net.opendasharchive.openarchive.features.main.QRScannerActivity
1716
import net.opendasharchive.openarchive.util.Utility
18-
import timber.log.Timber
1917

2018
class SnowbirdFragment : BaseSnowbirdFragment() {
2119
private val CANNED_URI = "save+dweb::?dht=82fd345d484393a96b6e0c5d5e17a85a61c9184cc5a3311ab069d6efa0bf1410&enc=6fa27396fe298f92c91013ac54d8f316c2d45dc3bed0edec73078040aa10feed&pk=f4b404d294817cf11ea7f8ef7231626e03b74f6fafe3271b53918608afa82d12&sk=5482a8f490081be684fbadb8bde7f0a99bab8acdcf1ec094826f0f18e327e399"
@@ -43,9 +41,8 @@ class SnowbirdFragment : BaseSnowbirdFragment() {
4341

4442
viewBinding.joinGroupButton.setOnClickListener {
4543
viewLifecycleOwner.lifecycleScope.launch {
46-
// startQRScanner()
44+
startQRScanner()
4745
// processScannedData(CANNED_URI)
48-
snowbirdGroupViewModel.fetchGroup("uloQH0mbWeZaJzF5ZySfOaGAmqb3cEnI1EoU8FDDXyw")
4946
}
5047
}
5148

@@ -59,26 +56,24 @@ class SnowbirdFragment : BaseSnowbirdFragment() {
5956
findNavController().navigate(SnowbirdFragmentDirections.navigateToSnowbirdCreateGroupScreen())
6057
}
6158

62-
viewLifecycleOwner.collectLifecycleFlow(snowbirdGroupViewModel.groups) { groups ->
63-
if (canNavigate) {
64-
canNavigate = false
65-
findNavController().navigate(SnowbirdFragmentDirections.navigateToSnowbirdGroupSelectionScreen())
66-
}
67-
}
68-
69-
viewLifecycleOwner.collectLifecycleFlow(snowbirdGroupViewModel.error) {
70-
handleError(it)
71-
}
59+
initializeViewModelObservers()
60+
}
7261

73-
viewLifecycleOwner.collectLifecycleFlow(snowbirdGroupViewModel.isProcessing) { isProcessing ->
74-
handleProcessingStatus(isProcessing)
62+
private fun initializeViewModelObservers() {
63+
lifecycleScope.launch {
64+
repeatOnLifecycle(Lifecycle.State.STARTED) {
65+
launch { snowbirdGroupViewModel.groupState.collect { state -> handleGroupStateUpdate(state) } }
66+
}
7567
}
7668
}
7769

78-
private fun handleError(error: SnowbirdError?) {
79-
error?.let {
80-
Toast.makeText(requireContext(), it.friendlyMessage, Toast.LENGTH_SHORT).show()
81-
Timber.d("Error = $it")
70+
private fun handleGroupStateUpdate(state: SnowbirdGroupViewModel.GroupState) {
71+
handleLoadingStatus(false)
72+
when (state) {
73+
is SnowbirdGroupViewModel.GroupState.Idle -> { /* Initial state */ }
74+
is SnowbirdGroupViewModel.GroupState.Loading -> handleLoadingStatus(true)
75+
is SnowbirdGroupViewModel.GroupState.Error -> handleError(state.error)
76+
else -> Unit
8277
}
8378
}
8479

0 commit comments

Comments
 (0)