Skip to content

Commit 62103c4

Browse files
authored
Merge pull request #352 from OpenHub-Store/iu-tweaks-enhancement
2 parents 0d44525 + e7c2b90 commit 62103c4

16 files changed

Lines changed: 345 additions & 215 deletions

File tree

core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/TweaksRepositoryImpl.kt

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,14 @@ import androidx.datastore.preferences.core.stringPreferencesKey
99
import kotlinx.coroutines.flow.Flow
1010
import kotlinx.coroutines.flow.map
1111
import zed.rainxch.core.domain.model.AppTheme
12+
import zed.rainxch.core.domain.model.DiscoveryPlatform
1213
import zed.rainxch.core.domain.model.FontTheme
1314
import zed.rainxch.core.domain.model.InstallerType
1415
import zed.rainxch.core.domain.repository.TweaksRepository
1516

1617
class TweaksRepositoryImpl(
1718
private val preferences: DataStore<Preferences>,
1819
) : TweaksRepository {
19-
private val THEME_KEY = stringPreferencesKey("app_theme")
20-
private val AMOLED_KEY = booleanPreferencesKey("amoled_theme")
21-
private val IS_DARK_THEME_KEY = booleanPreferencesKey("is_dark_theme")
22-
private val FONT_KEY = stringPreferencesKey("font_theme")
23-
private val AUTO_DETECT_CLIPBOARD_KEY = booleanPreferencesKey("auto_detect_clipboard_links")
24-
private val INSTALLER_TYPE_KEY = stringPreferencesKey("installer_type")
25-
private val AUTO_UPDATE_KEY = booleanPreferencesKey("auto_update_enabled")
26-
private val UPDATE_CHECK_INTERVAL_KEY = longPreferencesKey("update_check_interval_hours")
27-
private val INCLUDE_PRE_RELEASES_KEY = booleanPreferencesKey("include_pre_releases")
28-
private val LIQUID_GLASS_ENABLED_KEY = booleanPreferencesKey("liquid_glass_enabled")
29-
private val HIDE_SEEN_ENABLED_KEY = booleanPreferencesKey("hide_seen_enabled")
30-
3120
override fun getThemeColor(): Flow<AppTheme> =
3221
preferences.data.map { prefs ->
3322
val themeName = prefs[THEME_KEY]
@@ -156,7 +145,32 @@ class TweaksRepositoryImpl(
156145
}
157146
}
158147

148+
override fun getDiscoveryPlatform(): Flow<DiscoveryPlatform> =
149+
preferences.data.map { prefs ->
150+
val platform = prefs[DISCOVERY_PLATFORM_KEY]
151+
DiscoveryPlatform.fromName(platform)
152+
}
153+
154+
override suspend fun setDiscoveryPlatform(platform: DiscoveryPlatform) {
155+
preferences.edit { prefs ->
156+
prefs[DISCOVERY_PLATFORM_KEY] = platform.name
157+
}
158+
}
159+
159160
companion object {
160-
const val DEFAULT_UPDATE_CHECK_INTERVAL_HOURS = 6L
161+
private const val DEFAULT_UPDATE_CHECK_INTERVAL_HOURS = 6L
162+
163+
private val THEME_KEY = stringPreferencesKey("app_theme")
164+
private val AMOLED_KEY = booleanPreferencesKey("amoled_theme")
165+
private val IS_DARK_THEME_KEY = booleanPreferencesKey("is_dark_theme")
166+
private val FONT_KEY = stringPreferencesKey("font_theme")
167+
private val DISCOVERY_PLATFORM_KEY = stringPreferencesKey("discovery_platform")
168+
private val AUTO_DETECT_CLIPBOARD_KEY = booleanPreferencesKey("auto_detect_clipboard_links")
169+
private val INSTALLER_TYPE_KEY = stringPreferencesKey("installer_type")
170+
private val AUTO_UPDATE_KEY = booleanPreferencesKey("auto_update_enabled")
171+
private val UPDATE_CHECK_INTERVAL_KEY = longPreferencesKey("update_check_interval_hours")
172+
private val INCLUDE_PRE_RELEASES_KEY = booleanPreferencesKey("include_pre_releases")
173+
private val LIQUID_GLASS_ENABLED_KEY = booleanPreferencesKey("liquid_glass_enabled")
174+
private val HIDE_SEEN_ENABLED_KEY = booleanPreferencesKey("hide_seen_enabled")
161175
}
162176
}

core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/model/DiscoveryPlatform.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,9 @@ enum class DiscoveryPlatform {
66
Macos,
77
Windows,
88
Linux,
9+
;
10+
11+
companion object {
12+
fun fromName(name: String?): DiscoveryPlatform = DiscoveryPlatform.entries.find { it.name == name } ?: All
13+
}
914
}

core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/repository/TweaksRepository.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package zed.rainxch.core.domain.repository
22

33
import kotlinx.coroutines.flow.Flow
44
import zed.rainxch.core.domain.model.AppTheme
5+
import zed.rainxch.core.domain.model.DiscoveryPlatform
56
import zed.rainxch.core.domain.model.FontTheme
67
import zed.rainxch.core.domain.model.InstallerType
78

@@ -49,4 +50,8 @@ interface TweaksRepository {
4950
fun getHideSeenEnabled(): Flow<Boolean>
5051

5152
suspend fun setHideSeenEnabled(enabled: Boolean)
53+
54+
fun getDiscoveryPlatform(): Flow<DiscoveryPlatform>
55+
56+
suspend fun setDiscoveryPlatform(platform: DiscoveryPlatform)
5257
}

core/presentation/src/commonMain/composeResources/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@
453453
<string name="auth_hint_denied">You denied the authorization request. Try again if this was unintentional.</string>
454454
<string name="auth_check_status">I already authorized</string>
455455
<string name="auth_polling_status">Checking…</string>
456+
<string name="auth_rate_limited">Rate limited — retrying in %1$ds</string>
456457

457458
<!-- Read More / Show Less -->
458459
<string name="read_more">Read More</string>

feature/auth/data/src/commonMain/kotlin/zed/rainxch/auth/data/network/GitHubAuthApi.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@ object GitHubAuthApi {
117117
)
118118
}
119119
val status = res.status
120-
val text = res.body<String>()
120+
val text = res.bodyAsText()
121+
121122

122123
if (status !in HttpStatusCode.OK..HttpStatusCode.MultipleChoices) {
123124
return Result.failure(
@@ -129,8 +130,9 @@ object GitHubAuthApi {
129130

130131
try {
131132
val ok = json.decodeFromString(GithubDeviceTokenSuccessDto.serializer(), text)
133+
132134
Result.success(ok)
133-
} catch (_: Throwable) {
135+
} catch (e: Throwable) {
134136
val err = json.decodeFromString(GithubDeviceTokenErrorDto.serializer(), text)
135137
val message =
136138
buildString {
@@ -141,9 +143,11 @@ object GitHubAuthApi {
141143
append(desc)
142144
}
143145
}
146+
144147
Result.failure(IllegalStateException(message))
145148
}
146149
} catch (e: Exception) {
150+
147151
Result.failure(e)
148152
}
149153
}

feature/auth/data/src/commonMain/kotlin/zed/rainxch/auth/data/repository/AuthenticationRepositoryImpl.kt

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import kotlinx.coroutines.isActive
1111
import kotlinx.coroutines.withContext
1212
import zed.rainxch.auth.data.network.GitHubAuthApi
1313
import zed.rainxch.auth.domain.repository.AuthenticationRepository
14+
import zed.rainxch.auth.domain.repository.DevicePollResult
1415
import zed.rainxch.core.data.data_source.TokenStore
1516
import zed.rainxch.core.data.mappers.toData
1617
import zed.rainxch.core.data.mappers.toDomain
@@ -223,7 +224,7 @@ class AuthenticationRepositoryImpl(
223224
}
224225
}
225226

226-
override suspend fun pollDeviceTokenOnce(deviceCode: String): Result<GithubDeviceTokenSuccess?> =
227+
override suspend fun pollDeviceTokenOnce(deviceCode: String): DevicePollResult =
227228
withContext(Dispatchers.IO) {
228229
val clientId = BuildKonfig.GITHUB_CLIENT_ID
229230
try {
@@ -233,48 +234,53 @@ class AuthenticationRepositoryImpl(
233234
if (success != null) {
234235
logger.debug("✅ Single poll: Token received! Saving...")
235236
saveTokenWithVerification(success)
236-
Result.success(success)
237+
DevicePollResult.Success(success)
237238
} else {
238239
val error = res.exceptionOrNull()
239240
val errorMsg = (error?.message ?: "").lowercase()
240241

241242
when {
242-
"authorization_pending" in errorMsg || "slow_down" in errorMsg -> {
243-
Result.success(null)
243+
"slow_down" in errorMsg -> {
244+
logger.debug("⚠️ GitHub says slow down")
245+
DevicePollResult.SlowDown
246+
}
247+
248+
"authorization_pending" in errorMsg -> {
249+
DevicePollResult.Pending
244250
}
245251

246252
"access_denied" in errorMsg -> {
247-
Result.failure(
253+
DevicePollResult.Failed(
248254
Exception("Authentication was denied. Please try again if this was a mistake."),
249255
)
250256
}
251257

252258
"expired_token" in errorMsg ||
253259
"expired_device_code" in errorMsg ||
254260
"token_expired" in errorMsg -> {
255-
Result.failure(
261+
DevicePollResult.Failed(
256262
Exception("Authorization code expired. Please try again."),
257263
)
258264
}
259265

260266
"bad_verification_code" in errorMsg ||
261267
"incorrect_device_code" in errorMsg -> {
262-
Result.failure(
268+
DevicePollResult.Failed(
263269
Exception("Invalid verification code. Please restart authentication."),
264270
)
265271
}
266272

267273
else -> {
268274
logger.debug("⚠️ Single poll unknown error: $errorMsg")
269-
Result.success(null)
275+
DevicePollResult.Pending
270276
}
271277
}
272278
}
273279
} catch (e: CancellationException) {
274280
throw e
275281
} catch (e: Exception) {
276282
logger.debug("⚠️ Single poll network error: ${e.message}")
277-
Result.success(null)
283+
DevicePollResult.Pending
278284
}
279285
}
280286

feature/auth/domain/src/commonMain/kotlin/zed/rainxch/auth/domain/repository/AuthenticationRepository.kt

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@ interface AuthenticationRepository {
1111

1212
suspend fun awaitDeviceToken(start: GithubDeviceStart): GithubDeviceTokenSuccess
1313

14-
/**
15-
* Single poll attempt. Returns:
16-
* - [Result.success] with non-null [GithubDeviceTokenSuccess] if user authorized
17-
* - [Result.success] with null if authorization is still pending (keep polling)
18-
* - [Result.failure] on terminal errors (denied, expired, invalid code)
19-
*/
20-
suspend fun pollDeviceTokenOnce(deviceCode: String): Result<GithubDeviceTokenSuccess?>
14+
suspend fun pollDeviceTokenOnce(deviceCode: String): DevicePollResult
15+
}
16+
17+
sealed interface DevicePollResult {
18+
data class Success(val token: GithubDeviceTokenSuccess) : DevicePollResult
19+
20+
data object Pending : DevicePollResult
21+
22+
data object SlowDown : DevicePollResult
23+
24+
data class Failed(val error: Throwable) : DevicePollResult
2125
}

feature/auth/presentation/src/commonMain/kotlin/zed/rainxch/auth/presentation/AuthenticationRoot.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ import zed.rainxch.githubstore.core.presentation.res.auth_check_status
8282
import zed.rainxch.githubstore.core.presentation.res.auth_code_expires_in
8383
import zed.rainxch.githubstore.core.presentation.res.auth_error_with_message
8484
import zed.rainxch.githubstore.core.presentation.res.auth_polling_status
85+
import zed.rainxch.githubstore.core.presentation.res.auth_rate_limited
8586
import zed.rainxch.githubstore.core.presentation.res.continue_as_guest
8687
import zed.rainxch.githubstore.core.presentation.res.copy_code
8788
import zed.rainxch.githubstore.core.presentation.res.enter_code_on_github
@@ -532,6 +533,15 @@ private fun StateDevicePrompt(
532533
)
533534
}
534535

536+
if (state.pollIntervalSec > 0) {
537+
Spacer(Modifier.height(8.dp))
538+
Text(
539+
text = stringResource(Res.string.auth_rate_limited, state.pollIntervalSec),
540+
style = MaterialTheme.typography.labelSmall,
541+
color = MaterialTheme.colorScheme.onSurfaceVariant,
542+
)
543+
}
544+
535545
Spacer(Modifier.weight(2f))
536546
}
537547
}

feature/auth/presentation/src/commonMain/kotlin/zed/rainxch/auth/presentation/AuthenticationState.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ data class AuthenticationState(
77
val copied: Boolean = false,
88
val info: String? = null,
99
val isPolling: Boolean = false,
10+
val pollIntervalSec: Int = 0,
1011
)

0 commit comments

Comments
 (0)