From d0bab7f849e152e0da7065e0a4c1b20a9973bcfd Mon Sep 17 00:00:00 2001 From: Adrian Torchiana Date: Mon, 25 Aug 2025 22:21:18 -0700 Subject: [PATCH 01/10] feat: Add "Talk to Gemini" hint option This commit introduces a new feature that allows users to get hints for puzzles by launching the Gemini app. - A "Talk to Gemini" button has been added to the puzzle screen. - When clicked, the puzzle text is copied to the clipboard, and an Intent is fired to open the Gemini app. - If the Gemini app is not installed, the user is redirected to the Play Store. - The minimum SDK version has been increased to 19 to support the new testing libraries. - An end-to-end test has been added to verify the new functionality. --- app/build.gradle | 5 +- .../atorch/statspuzzles/SolvePuzzleTest.java | 50 +++++++++++++++++++ .../java/atorch/statspuzzles/SolvePuzzle.java | 32 ++++++++++++ .../main/res/layout/fragment_solve_puzzle.xml | 10 ++++ app/src/main/res/values-ar/strings.xml | 3 ++ app/src/main/res/values-de/strings.xml | 3 ++ app/src/main/res/values-es/strings.xml | 3 ++ app/src/main/res/values/strings.xml | 3 ++ 8 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 app/src/androidTest/java/atorch/statspuzzles/SolvePuzzleTest.java diff --git a/app/build.gradle b/app/build.gradle index fbbc57c..66d6eb5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,7 +5,7 @@ android { defaultConfig { applicationId "atorch.statspuzzles" - minSdkVersion 16 + minSdkVersion 19 targetSdkVersion 34 versionCode 32 versionName "4.0" @@ -37,7 +37,8 @@ dependencies { implementation 'androidx.navigation:navigation-ui:2.5.3' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' + androidTestImplementation 'androidx.test.espresso:espresso-intents:3.6.1' implementation fileTree(dir: 'libs', include: ['*.aar', '*.jar'], exclude: []) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10" } diff --git a/app/src/androidTest/java/atorch/statspuzzles/SolvePuzzleTest.java b/app/src/androidTest/java/atorch/statspuzzles/SolvePuzzleTest.java new file mode 100644 index 0000000..be4fecc --- /dev/null +++ b/app/src/androidTest/java/atorch/statspuzzles/SolvePuzzleTest.java @@ -0,0 +1,50 @@ +package atorch.statspuzzles; + +import android.content.Context; +import android.content.Intent; +import androidx.test.core.app.ActivityScenario; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.espresso.Espresso; +import androidx.test.espresso.intent.Intents; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.intent.Intents.intended; +import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction; +import static androidx.test.espresso.intent.matcher.IntentMatchers.hasData; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static org.hamcrest.CoreMatchers.allOf; + +@RunWith(AndroidJUnit4.class) +public class SolvePuzzleTest { + + @Before + public void setUp() { + Intents.init(); + } + + @After + public void tearDown() { + Intents.release(); + } + + @Test + public void geminiButton_launchesPlayStore() { + Context context = ApplicationProvider.getApplicationContext(); + Intent intent = new Intent(context, SolvePuzzle.class); + intent.putExtra(SolvePuzzle.LEVEL, 0); + ActivityScenario.launch(intent); + + Espresso.onView(withId(R.id.button_gemini_hint)).perform(click()); + + intended(allOf( + hasAction(Intent.ACTION_VIEW), + hasData("market://details?id=com.google.android.apps.gemini") + )); + } +} diff --git a/app/src/main/java/atorch/statspuzzles/SolvePuzzle.java b/app/src/main/java/atorch/statspuzzles/SolvePuzzle.java index 0bd6aa3..2cf0cf3 100644 --- a/app/src/main/java/atorch/statspuzzles/SolvePuzzle.java +++ b/app/src/main/java/atorch/statspuzzles/SolvePuzzle.java @@ -1,10 +1,14 @@ package atorch.statspuzzles; import android.app.AlertDialog; +import android.content.ClipData; +import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.content.res.Resources; +import android.net.Uri; import android.os.Bundle; import android.text.SpannableString; import android.text.method.LinkMovementMethod; @@ -273,6 +277,9 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa hintButton.setVisibility(View.VISIBLE); hintButton.setOnClickListener(unused -> showHint()); + Button geminiHintButton = rootView.findViewById(R.id.button_gemini_hint); + geminiHintButton.setOnClickListener(v -> onGeminiHint()); + Button submitButton = rootView.findViewById(R.id.submit_answer); submitButton.setOnClickListener(this::onSubmit); @@ -324,6 +331,31 @@ private void showHint() { ((TextView) alert.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance()); } + private void onGeminiHint() { + String puzzleText = res.getPuzzle(level, puzzleIndex); + String prompt = "Can you give me a hint for this puzzle, without giving away the answer? Here's the puzzle: " + puzzleText; + + ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("puzzle", prompt); + clipboard.setPrimaryClip(clip); + + Toast.makeText(getActivity(), R.string.puzzle_copied_to_clipboard, Toast.LENGTH_SHORT).show(); + + String geminiPackageName = "com.google.android.apps.gemini"; + PackageManager pm = getActivity().getPackageManager(); + Intent intent = pm.getLaunchIntentForPackage(geminiPackageName); + if (intent != null) { + getActivity().startActivity(intent); + } else { + Toast.makeText(getActivity(), R.string.gemini_not_installed, Toast.LENGTH_SHORT).show(); + try { + getActivity().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + geminiPackageName))); + } catch (android.content.ActivityNotFoundException anfe) { + getActivity().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=" + geminiPackageName))); + } + } + } + private void onSubmit(View view) { ImageView checkMark = getView().findViewById(R.id.check_mark); checkMark.setVisibility(View.INVISIBLE); diff --git a/app/src/main/res/layout/fragment_solve_puzzle.xml b/app/src/main/res/layout/fragment_solve_puzzle.xml index 13066ce..65f13ec 100644 --- a/app/src/main/res/layout/fragment_solve_puzzle.xml +++ b/app/src/main/res/layout/fragment_solve_puzzle.xml @@ -100,6 +100,16 @@ android:textAlignment="center" android:text="@string/button_hint" /> +