Skip to content

Commit 6aac932

Browse files
committed
Move UI to model
1 parent fa58f54 commit 6aac932

5 files changed

Lines changed: 125 additions & 139 deletions

File tree

app/src/androidTest/java/at/bitfire/icsdroid/model/ValidationUseCaseTest.kt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,18 +99,16 @@ class ValidationUseCaseTest {
9999
private fun validate(iCal: String): ResourceInfo {
100100
MockServer.enqueue(content = iCal)
101101

102-
val model = ValidationUseCase(context, appHttpClientFactory)
103-
runBlocking {
102+
val validator = Validator(context, appHttpClientFactory)
103+
return runBlocking {
104104
// Wait until the validation completed
105-
model.validate(
105+
validator.validate(
106106
MockServer.uri(),
107107
null,
108108
null,
109109
null
110-
).join()
110+
)
111111
}
112-
113-
return model.uiState.result!!
114112
}
115113

116114
}

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

Lines changed: 18 additions & 7 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
@@ -27,7 +28,7 @@ import javax.inject.Inject
2728
class AddSubscriptionModel @Inject constructor(
2829
@param:ApplicationContext private val context: Context,
2930
private val db: AppDatabase,
30-
val validationUseCase: ValidationUseCase,
31+
val validator: Validator,
3132
val subscriptionSettingsUseCase: SubscriptionSettingsUseCase
3233
) : ViewModel() {
3334

@@ -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 verificationResult: ResourceInfo? = null
3942
)
4043

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

48-
fun resetValidationResult() = validationUseCase.resetResult()
51+
fun resetValidationResult() {
52+
uiState = uiState.copy(verificationResult = null)
53+
}
54+
4955
fun validateUrl(
5056
originalUri: Uri,
5157
username: String? = null,
5258
password: String? = null,
5359
customUserAgent: String? = null
54-
) = validationUseCase.validate(originalUri, username, password, customUserAgent)
60+
) = viewModelScope.launch {
61+
uiState = uiState.copy(isVerifyingUrl = true)
62+
val result = validator.validate(originalUri, username, password, customUserAgent)
63+
uiState = uiState.copy(verificationResult = result)
64+
uiState = uiState.copy(isVerifyingUrl = false)
65+
}
5566

5667
fun checkUrlIntroductionPage() {
57-
with(subscriptionSettingsUseCase) {
58-
if (validationUseCase.uiState.isVerifyingUrl) {
59-
setShowNextButton(true)
60-
} else {
68+
if (uiState.isVerifyingUrl) {
69+
setShowNextButton(true)
70+
} else {
71+
with(subscriptionSettingsUseCase) {
6172
val uri = validateUri(
6273
url = uiState.url,
6374
onSetUrl = ::setUrl,

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

Lines changed: 0 additions & 124 deletions
This file was deleted.
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
3+
*/
4+
5+
package at.bitfire.icsdroid.model
6+
7+
import android.content.Context
8+
import android.net.Uri
9+
import android.util.Log
10+
import at.bitfire.ical4android.Css3Color
11+
import at.bitfire.ical4android.Event
12+
import at.bitfire.ical4android.ICalendar
13+
import at.bitfire.icsdroid.AppHttpClient
14+
import at.bitfire.icsdroid.CalendarFetcher
15+
import at.bitfire.icsdroid.Constants
16+
import at.bitfire.icsdroid.HttpUtils.toURI
17+
import at.bitfire.icsdroid.HttpUtils.toUri
18+
import at.bitfire.icsdroid.ui.ResourceInfo
19+
import dagger.hilt.android.qualifiers.ApplicationContext
20+
import io.ktor.http.ContentType
21+
import io.ktor.http.charset
22+
import kotlinx.coroutines.Dispatchers
23+
import kotlinx.coroutines.withContext
24+
import net.fortuna.ical4j.model.property.Color
25+
import java.io.InputStream
26+
import java.io.InputStreamReader
27+
import javax.inject.Inject
28+
import javax.inject.Singleton
29+
30+
@Singleton
31+
class Validator @Inject constructor(
32+
@param:ApplicationContext private val context: Context,
33+
private val appHttpClientFactory: AppHttpClient.Factory
34+
) {
35+
36+
suspend fun validate(
37+
originalUri: Uri,
38+
username: String?,
39+
password: String?,
40+
customUserAgent: String?
41+
): ResourceInfo = withContext(Dispatchers.IO) {
42+
Log.i(Constants.TAG, "Validating Webcal feed $originalUri (authentication: $username, customUserAgent: $customUserAgent)")
43+
44+
val info = ResourceInfo(originalUri)
45+
46+
val appHttpClient = appHttpClientFactory.create(customUserAgent)
47+
val downloader = object: CalendarFetcher(context, originalUri, appHttpClient) {
48+
override suspend fun onSuccess(
49+
data: InputStream,
50+
contentType: ContentType?,
51+
eTag: String?,
52+
lastModified: Long?,
53+
displayName: String?
54+
) {
55+
InputStreamReader(data, contentType?.charset() ?: Charsets.UTF_8).use { reader ->
56+
val properties = mutableMapOf<String, String>()
57+
val events = Event.eventsFromReader(reader, properties)
58+
59+
info.calendarName = properties[ICalendar.CALENDAR_NAME] ?: displayName
60+
info.calendarColor =
61+
// try COLOR first
62+
properties[Color.PROPERTY_NAME]?.let { colorValue ->
63+
Css3Color.colorFromString(colorValue)
64+
} ?:
65+
// try X-APPLE-CALENDAR-COLOR second
66+
try {
67+
properties[ICalendar.CALENDAR_COLOR]?.let { colorValue ->
68+
Css3Color.colorFromString(colorValue)
69+
}
70+
} catch (e: IllegalArgumentException) {
71+
Log.w(Constants.TAG, "Couldn't parse calendar COLOR", e)
72+
null
73+
}
74+
info.eventsFound = events.size
75+
}
76+
}
77+
78+
override suspend fun onNewPermanentUrl(target: Uri) {
79+
Log.i(Constants.TAG, "Got permanent redirect when validating, saving new URL: $target")
80+
val location = uri.toURI().resolve(target.toURI())
81+
info.uri = location.toUri()
82+
}
83+
84+
override suspend fun onError(error: Exception) {
85+
Log.e(Constants.TAG, "Couldn't validate calendar", error)
86+
info.exception = error
87+
}
88+
}
89+
90+
downloader.username = username
91+
downloader.password = password
92+
93+
// directly ask for confirmation of custom certificates
94+
downloader.inForeground = true
95+
96+
downloader.fetch()
97+
98+
return@withContext info
99+
}
100+
101+
}

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
@@ -142,8 +142,8 @@ fun AddSubscriptionScreen(
142142
}
143143
}
144144

145-
val isVerifyingUrl = model.validationUseCase.uiState.isVerifyingUrl
146-
val validationResult = model.validationUseCase.uiState.result
145+
val isVerifyingUrl = model.uiState.isVerifyingUrl
146+
val validationResult = model.uiState.verificationResult
147147

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

0 commit comments

Comments
 (0)