package org.terst.feedreader.ui import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import org.terst.feedreader.data.AppDatabase import org.terst.feedreader.data.ArticleEntity import org.terst.feedreader.data.Net import org.terst.feedreader.data.Settings import org.terst.feedreader.readability.ReaderExtractor import org.terst.feedreader.stash.StashClient import org.terst.feedreader.stash.StashSaveResult import java.security.MessageDigest private val URL_REGEX = Regex("""https?://\S+""") /** * Share-sheet target: a URL shared from any other app lands here. Extracts * the article on-device (so it's readable offline immediately, independent * of Stash), stores it locally as a standalone saved article (feedId=null, * not tied to any subscription), and best-effort archives it to Stash. */ class ShareReceiverActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val sharedText = intent?.getStringExtra(android.content.Intent.EXTRA_TEXT).orEmpty() val url = URL_REGEX.find(sharedText)?.value setContent { MaterialTheme { ShareReceiverContent(url = url, onDone = { finish() }) } } } } @Composable private fun ShareReceiverContent(url: String?, onDone: () -> Unit) { val context = LocalContext.current var status by remember { mutableStateOf(if (url == null) "No link found in shared content" else "Saving…") } var done by remember { mutableStateOf(url == null) } LaunchedEffect(url) { if (url == null) return@LaunchedEffect val db = AppDatabase.get(context) val extracted = ReaderExtractor.extract(context, url) val id = MessageDigest.getInstance("SHA-256").digest(url.toByteArray()).joinToString("") { "%02x".format(it) } db.articleDao().upsert( ArticleEntity( id = id, feedId = null, title = extracted?.title ?: url, link = url, extractedContent = extracted?.content, isSaved = true, createdAt = System.currentTimeMillis() ) ) val settings = Settings(context) val client = StashClient(Net.client, settings.stashBaseUrlNow(), settings.stashApiKeyNow()) status = when (val result = client.saveArticle(url, extracted?.title)) { is StashSaveResult.Saved -> { db.articleDao().setStashArtifactId(id, result.artifact.id) "Saved for offline reading and archived to Stash" } is StashSaveResult.AlreadySaved -> { db.articleDao().setStashArtifactId(id, result.existingId) "Saved for offline reading (already in Stash)" } is StashSaveResult.Failed -> "Saved for offline reading. Stash archive failed: ${result.message}" } done = true } Column(modifier = Modifier.padding(24.dp)) { Card(modifier = Modifier.padding(8.dp)) { Column(modifier = Modifier.padding(20.dp)) { Text("Feeds", style = MaterialTheme.typography.titleMedium) Spacer(modifier = Modifier.height(8.dp)) Text(status) if (!done) { Spacer(modifier = Modifier.height(8.dp)) CircularProgressIndicator() } else { Spacer(modifier = Modifier.height(8.dp)) Button(onClick = onDone) { Text("Done") } } } } } }