diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7aa0cfc..8aeb80a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,12 @@ + + android:enableOnBackInvokedCallback="true" + android:configChanges="uiMode" + android:documentLaunchMode="intoExisting"> + android:theme="@style/Theme.Breadboard"> + + + + + + + android:theme="@style/Theme.Breadboard"> diff --git a/app/src/main/java/moe/apex/breadboard/MainActivity.kt b/app/src/main/java/moe/apex/breadboard/MainActivity.kt index b65564f..313d2f6 100644 --- a/app/src/main/java/moe/apex/breadboard/MainActivity.kt +++ b/app/src/main/java/moe/apex/breadboard/MainActivity.kt @@ -35,6 +35,7 @@ import moe.apex.breadboard.navigation.Favourites import moe.apex.breadboard.navigation.Home import moe.apex.breadboard.navigation.Navigation import moe.apex.breadboard.navigation.Results +import moe.apex.breadboard.navigation.ReverseSearch import moe.apex.breadboard.navigation.Search import moe.apex.breadboard.preferences.DarkTheme import moe.apex.breadboard.preferences.ImageSource @@ -90,6 +91,19 @@ class MainActivity : SingletonImageLoader.Factory, ComponentActivity(), VolumeBu private fun determineDestination(intent: Intent): Any? { + // Handle Intent.ACTION_SEND intent for reverse search + if (intent.action == Intent.ACTION_SEND) { + /* Sometimes a shared link may contain an image clipData of the favicon, so we must + prioritise the text over the clipData. Providing them both would be bad. */ + intent.extras?.getString(Intent.EXTRA_TEXT)?.let { initialImageUrl -> + return ReverseSearch(initialImageUrl = initialImageUrl) + } + + intent.clipData?.getItemAt(0)?.uri?.let { initialFileUri -> + return ReverseSearch(initialFileUri = initialFileUri.toString()) + } + } + return when (intent.getStringExtra("destination")) { "artist" -> maybePrepareArtistDestination(intent) "search" -> maybePrepareResultsDestination(intent) diff --git a/app/src/main/java/moe/apex/breadboard/navigation/Destinations.kt b/app/src/main/java/moe/apex/breadboard/navigation/Destinations.kt index f28e050..c44a1d5 100644 --- a/app/src/main/java/moe/apex/breadboard/navigation/Destinations.kt +++ b/app/src/main/java/moe/apex/breadboard/navigation/Destinations.kt @@ -88,7 +88,7 @@ object FollowedArtists object Search @Serializable -object ReverseSearch +data class ReverseSearch(val initialImageUrl: String? = null, val initialFileUri: String? = null) @Serializable data class Results( diff --git a/app/src/main/java/moe/apex/breadboard/navigation/Navigation.kt b/app/src/main/java/moe/apex/breadboard/navigation/Navigation.kt index ab90787..00214a6 100644 --- a/app/src/main/java/moe/apex/breadboard/navigation/Navigation.kt +++ b/app/src/main/java/moe/apex/breadboard/navigation/Navigation.kt @@ -202,8 +202,8 @@ fun Navigation(navController: NavHostController, startDestination: Any = Search) }, onClick = { if (!currentRoute.routeIs(ReverseSearch::class)) { - navController.navigate(ReverseSearch) { - popUpTo(ReverseSearch) { inclusive = true } + navController.navigate(ReverseSearch()) { + popUpTo(ReverseSearch()) { inclusive = true } } } } @@ -288,7 +288,10 @@ fun Navigation(navController: NavHostController, startDestination: Any = Search) } composable { HomeScreen(navController) { isNavigationBarVisible = it } } composable { SearchScreen(navController, focusRequester) } - composable { ReverseSearchScreen(navController) } + composable { + val args = it.toRoute() + ReverseSearchScreen(navController, args.initialImageUrl, args.initialFileUri) + } composable { val args = it.toRoute() SearchResults(navController, args.source, args.tags) diff --git a/app/src/main/java/moe/apex/breadboard/saucenao/ReverseSearchScreen.kt b/app/src/main/java/moe/apex/breadboard/saucenao/ReverseSearchScreen.kt index 4e4f51d..2392a64 100644 --- a/app/src/main/java/moe/apex/breadboard/saucenao/ReverseSearchScreen.kt +++ b/app/src/main/java/moe/apex/breadboard/saucenao/ReverseSearchScreen.kt @@ -80,14 +80,18 @@ import moe.apex.breadboard.util.showToast @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @Composable -fun ReverseSearchScreen(navController: NavController) { +fun ReverseSearchScreen( + navController: NavController, + initialImageUrl: String? = null, + initialFileUri: String? = null +) { val context = LocalContext.current val clipboard = LocalClipboard.current val prefs = LocalPreferences.current val scope = rememberCoroutineScope() - var imageUrl by rememberSaveable { mutableStateOf("") } - var selectedFileUri by rememberSaveable { mutableStateOf(null) } + var imageUrl by rememberSaveable { mutableStateOf(initialImageUrl ?: "") } + var selectedFileUri by rememberSaveable { mutableStateOf(initialFileUri?.toUri()) } val imagePickerLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.GetContent() @@ -197,13 +201,26 @@ fun ReverseSearchScreen(navController: NavController) { IconButton( onClick = { scope.launch { - val clipboardText = + val clipboardItem = clipboard.nativeClipboard.primaryClip?.getItemAt( 0 - )?.text?.toString() - if (clipboardText != null) { - imageUrl = clipboardText - selectedFileUri = null + ) + + if (clipboardItem != null) { + val uri = clipboardItem.uri + val mimeType = uri?.let { + context.contentResolver.getType(it) + } + + if (mimeType?.startsWith("image/") == true) { + selectedFileUri = uri + } else { + /* onValueChange does not trigger when you mutate + imageUrl via code, so we still have to reset + selectedFileUri here. */ + selectedFileUri = null + imageUrl = clipboardItem.text.toString() + } } else { showToast(context, "Nothing on the clipboard") }