feat(ui): add feed via system share sheet (#618)

This commit is contained in:
junkfood 2024-02-14 21:27:51 +08:00 committed by GitHub
parent 8f4d24157e
commit 4c7bea918d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 122 additions and 9 deletions

View File

@ -27,17 +27,52 @@
<activity
android:name=".infrastructure.android.MainActivity"
android:exported="true"
android:launchMode="singleInstance"
android:theme="@style/Theme.Reader">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="*" />
<data android:mimeType="text/xml" />
<data android:mimeType="application/rss+xml" />
<data android:mimeType="application/atom+xml" />
<data android:mimeType="application/xml" />
<data android:mimeType="application/json" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
<activity
android:name=".infrastructure.android.CrashReportActivity"
android:exported="false"
android:theme="@style/Theme.Reader">
</activity>
<provider

View File

@ -1,5 +1,6 @@
package me.ash.reader.infrastructure.android
import android.content.Intent
import android.database.CursorWindow
import android.os.Build
import android.os.Bundle
@ -9,7 +10,10 @@ import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.core.util.Consumer
import androidx.core.view.WindowCompat
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.profileinstaller.ProfileInstallerInitializer
import coil.ImageLoader
import coil.compose.LocalImageLoader
@ -20,8 +24,8 @@ import me.ash.reader.infrastructure.preference.LanguagesPreference
import me.ash.reader.infrastructure.preference.SettingsProvider
import me.ash.reader.ui.ext.languages
import me.ash.reader.ui.page.common.HomeEntry
import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeViewModel
import java.lang.reflect.Field
import java.util.Locale
import javax.inject.Inject
@ -30,7 +34,6 @@ import javax.inject.Inject
*/
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var imageLoader: ImageLoader
@ -67,10 +70,39 @@ class MainActivity : AppCompatActivity() {
) {
AccountSettingsProvider(accountDao) {
SettingsProvider {
HomeEntry()
val subscribeViewModel: SubscribeViewModel = hiltViewModel()
DisposableEffect(this) {
val listener = Consumer<Intent> { intent ->
intent.getTextOrNull()?.let {
subscribeViewModel.handleSharedUrlFromIntent(it)
}
}
addOnNewIntentListener(listener)
onDispose {
removeOnNewIntentListener(listener)
}
}
HomeEntry(subscribeViewModel = subscribeViewModel)
}
}
}
}
}
}
private fun Intent.getTextOrNull(): String? {
return when (action) {
Intent.ACTION_VIEW -> {
dataString
}
Intent.ACTION_SEND -> {
getStringExtra(Intent.EXTRA_TEXT)
?.also { removeExtra(Intent.EXTRA_TEXT) }
}
else -> null
}
}

View File

@ -21,6 +21,7 @@ import me.ash.reader.infrastructure.preference.LocalReadingDarkTheme
import me.ash.reader.ui.ext.*
import me.ash.reader.ui.page.home.HomeViewModel
import me.ash.reader.ui.page.home.feeds.FeedsPage
import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeViewModel
import me.ash.reader.ui.page.home.flow.FlowPage
import me.ash.reader.ui.page.home.reading.ReadingPage
import me.ash.reader.ui.page.settings.SettingsPage
@ -42,10 +43,12 @@ import me.ash.reader.ui.theme.AppTheme
@Composable
fun HomeEntry(
homeViewModel: HomeViewModel = hiltViewModel(),
subscribeViewModel: SubscribeViewModel = hiltViewModel(),
) {
val context = LocalContext.current
var isReadingPage by rememberSaveable { mutableStateOf(false) }
val filterUiState = homeViewModel.filterUiState.collectAsStateValue()
val subscribeUiState = subscribeViewModel.subscribeUiState.collectAsStateValue()
val navController = rememberAnimatedNavController()
val intent by rememberSaveable { mutableStateOf(context.findActivity()?.intent) }
@ -81,7 +84,23 @@ fun HomeEntry(
Log.i("RLog", "currentBackStackEntry: ${navController.currentDestination?.route}")
// Animation duration takes 310 ms
delay(310L)
isReadingPage = navController.currentDestination?.route == "${RouteName.READING}/{articleId}"
isReadingPage =
navController.currentDestination?.route == "${RouteName.READING}/{articleId}"
}
}
DisposableEffect(subscribeUiState.shouldNavigateToFeedPage) {
if (subscribeUiState.shouldNavigateToFeedPage) {
if (navController.currentDestination?.route != RouteName.FEEDS) {
navController.popBackStack(
route = RouteName.FEEDS,
inclusive = false,
saveState = true
)
}
}
onDispose {
subscribeViewModel.onIntentConsumed()
}
}
@ -126,7 +145,11 @@ fun HomeEntry(
// Home
forwardAndBackwardComposable(route = RouteName.FEEDS) {
FeedsPage(navController = navController, homeViewModel = homeViewModel)
FeedsPage(
navController = navController,
homeViewModel = homeViewModel,
subscribeViewModel = subscribeViewModel
)
}
forwardAndBackwardComposable(route = RouteName.FLOW) {
FlowPage(

View File

@ -322,7 +322,7 @@ fun FeedsPage(
}
)
SubscribeDialog()
SubscribeDialog(subscribeViewModel = subscribeViewModel)
GroupOptionDrawer()
FeedOptionDrawer()

View File

@ -7,6 +7,7 @@ import com.rometools.rome.feed.synd.SyndFeed
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -52,7 +53,7 @@ class SubscribeViewModel @Inject constructor(
searchJob?.cancel()
searchJob = null
_subscribeUiState.update {
SubscribeUiState().copy(title = androidStringsHelper.getString(R.string.subscribe))
SubscribeUiState(title = androidStringsHelper.getString(R.string.subscribe))
}
}
@ -75,7 +76,9 @@ class SubscribeViewModel @Inject constructor(
if (_subscribeUiState.value.newGroupContent.isNotBlank()) {
applicationScope.launch {
// TODO: How to add a single group without no feeds via Google Reader API?
selectedGroup(rssService.get().addGroup(null, _subscribeUiState.value.newGroupContent))
selectedGroup(
rssService.get().addGroup(null, _subscribeUiState.value.newGroupContent)
)
hideNewGroupDialog()
_subscribeUiState.update { it.copy(newGroupContent = "") }
}
@ -139,7 +142,8 @@ class SubscribeViewModel @Inject constructor(
_subscribeUiState.update {
it.copy(
title = androidStringsHelper.getString(R.string.subscribe),
errorMessage = e.message ?: androidStringsHelper.getString(R.string.unknown),
errorMessage = e.message
?: androidStringsHelper.getString(R.string.unknown),
lockLinkInput = false,
)
}
@ -175,6 +179,24 @@ class SubscribeViewModel @Inject constructor(
_subscribeUiState.update { it.copy(newGroupContent = content) }
}
fun handleSharedUrlFromIntent(url: String) {
viewModelScope.launch {
_subscribeUiState.update {
it.copy(
visible = true,
shouldNavigateToFeedPage = true,
linkContent = url,
errorMessage = "",
)
}
delay(50)
}.invokeOnCompletion { search() }
}
fun onIntentConsumed() {
_subscribeUiState.update { it.copy(shouldNavigateToFeedPage = false) }
}
fun showDrawer() {
_subscribeUiState.update { it.copy(visible = true) }
}
@ -238,4 +260,5 @@ data class SubscribeUiState(
val isSearchPage: Boolean = true,
val newName: String = "",
val renameDialogVisible: Boolean = false,
val shouldNavigateToFeedPage: Boolean = false,
)