Skip to content
Merged

v2 #1993

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ app/gplay/release/

# Cruft
technotes/.obsidian/
**/.idea/
.idea/runConfigurations.xml
.idea/markdown.xml
.kotlin/sessions/
Expand Down
39 changes: 39 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import com.android.build.gradle.internal.tasks.factory.dependsOn
import java.security.SecureRandom
import java.util.Properties

plugins {
Expand Down Expand Up @@ -193,4 +194,42 @@ tasks.register("useGMSDebugFile") {
}
}

val generateSecretKey = tasks.register("generateSecretKey") {
val outputDir = layout.buildDirectory.dir("generated/source/secrets/com/capyreader/app")
outputs.dir(outputDir)

doLast {
val username = secrets.getProperty("extract_username", "")
val secret = secrets.getProperty("extract_secret", "")
val salt = ByteArray(64).also { SecureRandom().nextBytes(it) }

fun encode(value: String) = value.toByteArray()
.mapIndexed { i, b -> (b.toInt() xor salt[i % salt.size].toInt()).toByte() }
.toByteArray()

fun ByteArray.toHexLiteral() = joinToString { "0x%02x.toByte()".format(it) }

val file = outputDir.get().file("SecretKey.kt").asFile
file.parentFile.mkdirs()
file.writeText(
"""
package com.capyreader.app

internal object SecretKey {
private val salt = byteArrayOf(${salt.toHexLiteral()})

private fun decode(encoded: ByteArray) =
String(ByteArray(encoded.size) { i -> (encoded[i].toInt() xor salt[i % salt.size].toInt()).toByte() })

val extractUsername = decode(byteArrayOf(${encode(username).toHexLiteral()}))
val extractSecret = decode(byteArrayOf(${encode(secret).toHexLiteral()}))
}
""".trimIndent()
)
}
}

project.tasks.preBuild.dependsOn("useGMSDebugFile")
project.tasks.preBuild.dependsOn("generateSecretKey")

android.sourceSets["main"].kotlin.srcDir(layout.buildDirectory.dir("generated/source/secrets"))
2 changes: 1 addition & 1 deletion app/src/main/java/com/capyreader/app/AddLinkActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class AddLinkActivity : BaseActivity() {
AddLinkScreen(
defaultQueryURL = defaultQueryURL,
pageTitle = pageTitle,
supportsPages = account.source.supportsPages,
supportsReadLater = account.source.supportsReadLater,
onBack = {
finish()
}
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/capyreader/app/CommonModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ internal val common = module {
clientCertManager = get(),
userAgent = WebSettings.getDefaultUserAgent(androidContext()),
acceptLanguage = Locale.getDefault().toAcceptLanguageTag(),
extractUsername = SecretKey.extractUsername,
extractSecret = SecretKey.extractSecret,
)
}
single { AppPreferences(get()) }
Expand Down
11 changes: 9 additions & 2 deletions app/src/main/java/com/capyreader/app/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package com.capyreader.app
import android.content.Intent
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import com.capyreader.app.notifications.NotificationHelper
import com.capyreader.app.preferences.AppPreferences
import com.capyreader.app.ui.App
Expand All @@ -13,21 +16,25 @@ import org.koin.android.ext.android.inject
class MainActivity : BaseActivity() {
val appPreferences by inject<AppPreferences>()

private var pendingArticleID by mutableStateOf<String?>(null)

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
NotificationHelper.openFromIntent(intent, appPreferences = appPreferences)
pendingArticleID = NotificationHelper.openFromIntent(intent, appPreferences = appPreferences)

setContent {
App(
startDestination = startDestination(),
appPreferences = appPreferences,
pendingArticleID = pendingArticleID,
onPendingArticleSelected = { pendingArticleID = null },
)
}
}

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
NotificationHelper.openFromIntent(intent, appPreferences = appPreferences)
pendingArticleID = NotificationHelper.openFromIntent(intent, appPreferences = appPreferences)
}

private fun startDestination(): Route {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ class NotificationHelper(
}
}

fun openFromIntent(intent: Intent, appPreferences: AppPreferences) {
fun openFromIntent(intent: Intent, appPreferences: AppPreferences): String? {
val openFromShowMore = intent.getBooleanExtra(UNREAD_ONLY_KEY, false)
val articleID = intent.getStringExtra(ARTICLE_ID_KEY)
val feedID = intent.getStringExtra(FEED_ID_KEY)
Expand All @@ -167,24 +167,22 @@ class NotificationHelper(
ArticleFilter.Articles(articleStatus = ArticleStatus.UNREAD)
)

appPreferences.articleID.delete()
return null
} else if (articleID != null && feedID != null) {
intent.replaceExtras(Bundle())

appPreferences.filter.getAndSet { currentFilter ->
ArticleFilter.Feeds(
feedID,
feedStatus = if (currentFilter.status != ArticleStatus.STARRED) {
currentFilter.status
} else {
ArticleStatus.UNREAD
},
feedStatus = currentFilter.status,
folderTitle = null
)
}

appPreferences.articleID.set(articleID)
return articleID
}

return null
}
}
}
Expand Down
21 changes: 16 additions & 5 deletions app/src/main/java/com/capyreader/app/preferences/AppPreferences.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,6 @@ class AppPreferences(context: Context) {
val refreshInterval: Preference<RefreshInterval>
get() = preferenceStore.getEnum("refresh_interval", RefreshInterval.default)

val articleID: Preference<String>
get() = preferenceStore.getString("article_id")

val crashReporting: Preference<Boolean>
get() = preferenceStore.getBoolean("enable_crash_reporting", false)

Expand Down Expand Up @@ -82,8 +79,19 @@ class AppPreferences(context: Context) {
return preferenceStore.getBoolean("feed_group_${type.toString().lowercase()}", true)
}

val showTodayFilter: Preference<Boolean>
get() = preferenceStore.getBoolean("show_today_filter", true)
val homePage: Preference<HomePage>
get() = preferenceStore.getObject(
key = "home_page",
defaultValue = HomePage.default,
serializer = { Json.encodeToString(it) },
deserializer = {
try {
Json.decodeFromString(it)
} catch (e: Throwable) {
HomePage.default
}
}
)

val badgeStyle: Preference<BadgeStyle>
get() = preferenceStore.getEnum("badge_style", BadgeStyle.default)
Expand Down Expand Up @@ -199,5 +207,8 @@ class AppPreferences(context: Context) {
"after_read_all_behavior",
AfterReadAllBehavior.default
)

val hideReadArticles: Preference<Boolean>
get() = preferenceStore.getBoolean("article_list_hide_read", false)
}
}
41 changes: 41 additions & 0 deletions app/src/main/java/com/capyreader/app/preferences/HomePage.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.capyreader.app.preferences

import com.jocmp.capy.ArticleFilter
import com.jocmp.capy.ArticleStatus
import kotlinx.serialization.Serializable

@Serializable
sealed class HomePage {
@Serializable
data object Today : HomePage()

@Serializable
data object Unread : HomePage()

@Serializable
data object Starred : HomePage()

@Serializable
data object ReadLater : HomePage()

fun toArticleFilter(readLaterFeedID: String? = null): ArticleFilter {
return when (this) {
is Today -> ArticleFilter.Today(todayStatus = ArticleStatus.ALL)
is Unread -> ArticleFilter.Articles(articleStatus = ArticleStatus.UNREAD)
is Starred -> ArticleFilter.Starred()
is ReadLater -> if (readLaterFeedID != null) {
ArticleFilter.Feeds(
feedID = readLaterFeedID,
folderTitle = null,
feedStatus = ArticleStatus.ALL,
)
} else {
ArticleFilter.Articles(articleStatus = ArticleStatus.UNREAD)
}
}
}

companion object {
val default: HomePage = Today
}
}
8 changes: 7 additions & 1 deletion app/src/main/java/com/capyreader/app/ui/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import com.capyreader.app.unloadAccountModules
fun App(
startDestination: Route,
appPreferences: AppPreferences,
pendingArticleID: String? = null,
onPendingArticleSelected: () -> Unit = {},
) {
val navController = rememberNavController()

Expand Down Expand Up @@ -54,7 +56,11 @@ fun App(
unloadAccountModules()
}
)
articleGraph(navController = navController)
articleGraph(
navController = navController,
pendingArticleID = pendingArticleID,
onPendingArticleSelected = onPendingArticleSelected,
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@ val ArticleStatus.navigationTitle: Int
get() = when (this) {
ArticleStatus.ALL -> R.string.filter_all
ArticleStatus.UNREAD -> R.string.filter_unread
ArticleStatus.STARRED -> R.string.filter_starred
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,4 @@ val Source.savedSearchNavTitle: Int
R.string.freshrss_nav_headline_my_labels
} else {
R.string.nav_headline_saved_searches
}

val Source.folderNavTitle: Int
get() = if (this == Source.FRESHRSS) {
R.string.freshrss_nav_headline_categories
} else {
R.string.nav_headline_tags
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.autofill.ContentType
import androidx.compose.ui.autofill.contentType
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
Expand All @@ -50,7 +51,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.capyreader.app.R
import com.capyreader.app.ui.articles.feeds.IconDropdown
import androidx.compose.ui.autofill.contentType
import com.capyreader.app.ui.theme.CapyTheme
import com.jocmp.capy.accounts.Source

Expand Down Expand Up @@ -114,6 +114,7 @@ fun AuthFields(
),
modifier = Modifier
.fillMaxWidth()
.contentType(ContentType.Username)
.contentType(ContentType.EmailAddress)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,14 @@ fun AddLinkScreen(
onBack: () -> Unit,
defaultQueryURL: String,
pageTitle: String,
supportsPages: Boolean,
supportsReadLater: Boolean,
) {
val (successMessage, setSuccessMessage) = remember { mutableStateOf<String?>(null) }

AddLinkView(
defaultQueryURL = defaultQueryURL,
pageTitle = pageTitle,
supportsPages = supportsPages,
supportsReadLater = supportsReadLater,
onBack = onBack,
feedChoices = viewModel.feedChoices,
onSearchFeed = { url ->
Expand All @@ -66,7 +66,6 @@ fun AddLinkScreen(
viewModel.addFeed(
url = url,
onComplete = {
viewModel.selectFeed(it.id)
setSuccessMessage("feed")
},
)
Expand All @@ -92,7 +91,7 @@ fun AddLinkScreen(
fun AddLinkView(
defaultQueryURL: String,
pageTitle: String = "",
supportsPages: Boolean,
supportsReadLater: Boolean,
onBack: () -> Unit,
feedChoices: List<FeedOption> = emptyList(),
onSearchFeed: (url: String) -> Unit = {},
Expand All @@ -104,7 +103,7 @@ fun AddLinkView(
savePageError: String? = null,
successMessage: String? = null,
) {
val defaultTab = if (supportsPages && defaultQueryURL.isNotBlank()) {
val defaultTab = if (supportsReadLater && defaultQueryURL.isNotBlank()) {
AddLinkTab.SAVE_PAGE
} else {
AddLinkTab.ADD_FEED
Expand Down Expand Up @@ -140,7 +139,7 @@ fun AddLinkView(
Column(
modifier = Modifier.navigationBarsPadding()
) {
if (supportsPages && successMessage == null) {
if (supportsReadLater && successMessage == null) {
val tabs = AddLinkTab.entries

Row(
Expand Down Expand Up @@ -259,7 +258,7 @@ private enum class AddLinkTab(val title: Int, val icon: ImageVector) {
private fun AddLinkViewPreview() {
AddLinkView(
defaultQueryURL = "",
supportsPages = true,
supportsReadLater = true,
onBack = {},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,18 @@ package com.capyreader.app.ui.addintent
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.capyreader.app.preferences.AppPreferences
import com.jocmp.capy.Account
import com.jocmp.capy.ArticleFilter
import com.jocmp.capy.Feed
import com.jocmp.capy.accounts.AddFeedResult
import com.jocmp.capy.accounts.FeedOption
import com.jocmp.capy.accounts.Source
import com.jocmp.capy.common.launchIO
import com.jocmp.capy.common.withUIContext
import com.jocmp.capy.preferences.getAndSet
import okio.IOException
import java.net.UnknownHostException

class AddLinkViewModel(
private val account: Account,
private val appPreferences: AppPreferences,
) : ViewModel() {
private val _feedResult = mutableStateOf<AddFeedResult?>(null)
private val _feedLoading = mutableStateOf(false)
Expand Down Expand Up @@ -90,12 +86,6 @@ class AddLinkViewModel(
}
}

fun selectFeed(id: String) {
appPreferences.filter.getAndSet {
ArticleFilter.Feeds(feedID = id, folderTitle = null, it.status)
}
}

private val _pageLoading = mutableStateOf(false)
private val _pageError = mutableStateOf<String?>(null)

Expand Down
Loading
Loading