Skip to content

Commit f7f0078

Browse files
committed
Moved ui state out of ValidationUseCase
1 parent 6756169 commit f7f0078

4 files changed

Lines changed: 70 additions & 82 deletions

File tree

app/src/main/java/at/bitfire/icsdroid/CalendarFetcher.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ open class CalendarFetcher(
4444
var inForeground = false
4545

4646

47+
/**
48+
* Resolves the request on the given [uri].
49+
*
50+
* Blocks the current thread until completed.
51+
* Callback methods ([onSuccess], [onNotModified], [onRedirect], [onNewPermanentUrl] and
52+
* [onError]) will be run before returning this function.
53+
*/
4754
suspend fun fetch() {
4855
if (uri.scheme.equals("http", true) or uri.scheme.equals("https", true))
4956
fetchNetwork()
@@ -94,7 +101,7 @@ open class CalendarFetcher(
94101

95102

96103
/**
97-
* Fetch the file with Android SAF
104+
* Fetch the file with Android SAF. Blocks the current thread until the request is resolved.
98105
*/
99106
internal suspend fun fetchLocal() {
100107
Log.i(Constants.TAG, "Fetching local file $uri")
@@ -130,7 +137,7 @@ open class CalendarFetcher(
130137
}
131138

132139
/**
133-
* Fetch the file over network
140+
* Fetch the file over network. Blocks the current thread until the request is resolved.
134141
*/
135142
internal suspend fun fetchNetwork() {
136143
Log.i(Constants.TAG, "Fetching remote file $uri")

app/src/main/java/at/bitfire/icsdroid/model/AddSubscriptionModel.kt

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import at.bitfire.icsdroid.SyncWorker
1414
import at.bitfire.icsdroid.db.AppDatabase
1515
import at.bitfire.icsdroid.db.entity.Credential
1616
import at.bitfire.icsdroid.db.entity.Subscription
17+
import at.bitfire.icsdroid.ui.ResourceInfo
1718
import dagger.hilt.android.lifecycle.HiltViewModel
1819
import dagger.hilt.android.qualifiers.ApplicationContext
1920
import kotlinx.coroutines.Dispatchers
@@ -36,6 +37,8 @@ class AddSubscriptionModel @Inject constructor(
3637
val errorMessage: String? = null,
3738
val isCreating: Boolean = false,
3839
val showNextButton: Boolean = false,
40+
val isVerifyingUrl: Boolean = false,
41+
val result: ResourceInfo? = null,
3942
)
4043

4144
var uiState by mutableStateOf(UiState())
@@ -45,13 +48,20 @@ class AddSubscriptionModel @Inject constructor(
4548
uiState = uiState.copy(showNextButton = value)
4649
}
4750

48-
fun resetValidationResult() = validationUseCase.resetResult()
49-
fun validateUrl(originalUri: Uri, username: String? = null, password: String? = null) =
51+
fun resetValidationResult() {
52+
uiState = uiState.copy(result = null)
53+
}
54+
fun validateUrl(
55+
originalUri: Uri,
56+
username: String? = null,
57+
password: String? = null
58+
) = viewModelScope.launch {
5059
validationUseCase.validate(originalUri, username, password)
60+
}
5161

5262
fun checkUrlIntroductionPage() {
5363
with(subscriptionSettingsUseCase) {
54-
if (validationUseCase.uiState.isVerifyingUrl) {
64+
if (this@AddSubscriptionModel.uiState.isVerifyingUrl) {
5565
setShowNextButton(true)
5666
} else {
5767
val uri = validateUri(
Lines changed: 46 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,20 @@
1-
/*
2-
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
3-
*/
4-
51
package at.bitfire.icsdroid.model
62

73
import android.content.Context
84
import android.net.Uri
95
import android.util.Log
10-
import androidx.compose.runtime.getValue
11-
import androidx.compose.runtime.mutableStateOf
12-
import androidx.compose.runtime.setValue
136
import at.bitfire.ical4android.Css3Color
147
import at.bitfire.ical4android.Event
158
import at.bitfire.ical4android.ICalendar
9+
import at.bitfire.icsdroid.AppHttpClient
1610
import at.bitfire.icsdroid.CalendarFetcher
1711
import at.bitfire.icsdroid.Constants
18-
import at.bitfire.icsdroid.AppHttpClient
1912
import at.bitfire.icsdroid.HttpUtils.toURI
2013
import at.bitfire.icsdroid.HttpUtils.toUri
2114
import at.bitfire.icsdroid.ui.ResourceInfo
2215
import dagger.hilt.android.qualifiers.ApplicationContext
2316
import io.ktor.http.ContentType
2417
import io.ktor.http.charset
25-
import kotlinx.coroutines.CoroutineScope
26-
import kotlinx.coroutines.Dispatchers
27-
import kotlinx.coroutines.launch
2818
import net.fortuna.ical4j.model.property.Color
2919
import java.io.InputStream
3020
import java.io.InputStreamReader
@@ -36,86 +26,67 @@ class ValidationUseCase @Inject constructor(
3626
@param:ApplicationContext private val context: Context,
3727
private val appHttpClient: AppHttpClient,
3828
) {
39-
40-
data class UiState(
41-
val isVerifyingUrl: Boolean = false,
42-
val result: ResourceInfo? = null
43-
)
44-
45-
var uiState by mutableStateOf(UiState())
46-
private set
47-
48-
fun resetResult() {
49-
uiState = uiState.copy(result = null)
50-
}
51-
52-
fun validate(
29+
suspend fun validate(
5330
originalUri: Uri,
5431
username: String?,
5532
password: String?
56-
) = CoroutineScope(Dispatchers.IO).launch {
57-
try {
58-
Log.i(Constants.TAG, "Validating Webcal feed $originalUri (authentication: $username)")
59-
60-
uiState = uiState.copy(isVerifyingUrl = true)
61-
62-
val info = ResourceInfo(originalUri)
63-
val downloader = object: CalendarFetcher(context, originalUri, appHttpClient) {
64-
override suspend fun onSuccess(
65-
data: InputStream,
66-
contentType: ContentType?,
67-
eTag: String?,
68-
lastModified: Long?,
69-
displayName: String?
70-
) {
71-
InputStreamReader(data, contentType?.charset() ?: Charsets.UTF_8).use { reader ->
72-
val properties = mutableMapOf<String, String>()
73-
val events = Event.eventsFromReader(reader, properties)
74-
75-
info.calendarName = properties[ICalendar.CALENDAR_NAME] ?: displayName
76-
info.calendarColor =
33+
): ResourceInfo {
34+
Log.i(Constants.TAG, "Validating Webcal feed $originalUri (authentication: $username)")
35+
36+
val info = ResourceInfo(originalUri)
37+
val downloader = object: CalendarFetcher(context, originalUri, appHttpClient) {
38+
override suspend fun onSuccess(
39+
data: InputStream,
40+
contentType: ContentType?,
41+
eTag: String?,
42+
lastModified: Long?,
43+
displayName: String?
44+
) {
45+
InputStreamReader(data, contentType?.charset() ?: Charsets.UTF_8).use { reader ->
46+
val properties = mutableMapOf<String, String>()
47+
val events = Event.Companion.eventsFromReader(reader, properties)
48+
49+
info.calendarName = properties[ICalendar.Companion.CALENDAR_NAME] ?: displayName
50+
info.calendarColor =
7751
// try COLOR first
78-
properties[Color.PROPERTY_NAME]?.let { colorValue ->
79-
Css3Color.colorFromString(colorValue)
80-
} ?:
52+
properties[Color.PROPERTY_NAME]?.let { colorValue ->
53+
Css3Color.Companion.colorFromString(colorValue)
54+
} ?:
8155
// try X-APPLE-CALENDAR-COLOR second
8256
try {
83-
properties[ICalendar.CALENDAR_COLOR]?.let { colorValue ->
84-
Css3Color.colorFromString(colorValue)
57+
properties[ICalendar.Companion.CALENDAR_COLOR]?.let { colorValue ->
58+
Css3Color.Companion.colorFromString(colorValue)
8559
}
8660
} catch (e: IllegalArgumentException) {
8761
Log.w(Constants.TAG, "Couldn't parse calendar COLOR", e)
8862
null
8963
}
90-
info.eventsFound = events.size
91-
}
92-
93-
uiState = uiState.copy(result = info)
64+
info.eventsFound = events.size
9465
}
66+
}
9567

96-
override suspend fun onNewPermanentUrl(target: Uri) {
97-
Log.i(Constants.TAG, "Got permanent redirect when validating, saving new URL: $target")
98-
val location = uri.toURI().resolve(target.toURI())
99-
info.uri = location.toUri()
100-
}
68+
override suspend fun onNewPermanentUrl(target: Uri) {
69+
Log.i(Constants.TAG, "Got permanent redirect when validating, saving new URL: $target")
70+
val location = uri.toURI().resolve(target.toURI())
71+
info.uri = location.toUri()
72+
}
10173

102-
override suspend fun onError(error: Exception) {
103-
Log.e(Constants.TAG, "Couldn't validate calendar", error)
104-
info.exception = error
105-
uiState = uiState.copy(result = info)
106-
}
74+
override suspend fun onError(error: Exception) {
75+
Log.e(Constants.TAG, "Couldn't validate calendar", error)
76+
info.exception = error
10777
}
78+
}
10879

109-
downloader.username = username
110-
downloader.password = password
80+
downloader.username = username
81+
downloader.password = password
11182

112-
// directly ask for confirmation of custom certificates
113-
downloader.inForeground = true
83+
// directly ask for confirmation of custom certificates
84+
downloader.inForeground = true
11485

115-
downloader.fetch()
116-
} finally {
117-
uiState = uiState.copy(isVerifyingUrl = false)
118-
}
119-
}
86+
// downloader.fetch blocks the current thread until the request is completed
87+
downloader.fetch()
12088

121-
}
89+
// at this point, info's contents has already been updated correctly
90+
return info
91+
}
92+
}

app/src/main/java/at/bitfire/icsdroid/ui/screen/AddSubscriptionScreen.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ fun AddSubscriptionScreen(
7272
}
7373
}
7474

75-
val isVerifyingUrl = model.validationUseCase.uiState.isVerifyingUrl
76-
val validationResult = model.validationUseCase.uiState.result
75+
val isVerifyingUrl = model.uiState.isVerifyingUrl
76+
val validationResult = model.uiState.result
7777

7878
LaunchedEffect(validationResult) {
7979
Log.i("AddCalendarActivity", "Validation result updated: $validationResult")

0 commit comments

Comments
 (0)