diff --git a/IrisGlass/README.md b/IrisGlass/README.md index 9a0cdb3..3e53e91 100644 --- a/IrisGlass/README.md +++ b/IrisGlass/README.md @@ -20,7 +20,7 @@ Logcat tags: ## Using the feed UI - Tap the pinned LiveCard to open `FeedActivity`. - Swipe left/right to move through cards. -- Tap a card to open its actions (Glass-style overlay); `DISMISS` / `SNOOZE_*` are stored locally and stay hidden across restarts. +- Tap a card to open its actions (Glass-style overlay); `DISMISS` / `SNOOZE_*` remove the card from the current view and rely on the companion feed to keep it suppressed. ## FYI static cards (Weather) If the feed includes a card with `bucket="FYI"` and a `type` that equals `WEATHER_INFO` or contains `WEATHER` (e.g. `CURRENT_WEATHER`), the app publishes it as a Glass “static card” via an Android notification using a custom `RemoteViews` layout modeled after the “Google Now Weather” card style (see `app/src/main/res/layout/weather_static_card.xml` and `app/src/main/java/sh/nym/irisglass/WeatherStaticCardPublisher.java`). diff --git a/IrisGlass/app/src/main/java/sh/nym/irisglass/FeedActivity.java b/IrisGlass/app/src/main/java/sh/nym/irisglass/FeedActivity.java index 0681648..c6fb71c 100644 --- a/IrisGlass/app/src/main/java/sh/nym/irisglass/FeedActivity.java +++ b/IrisGlass/app/src/main/java/sh/nym/irisglass/FeedActivity.java @@ -17,7 +17,6 @@ public final class FeedActivity extends Activity { private CardScrollView cardScrollView; private FeedAdapter adapter; - private SuppressionStore suppressionStore; private FeedItem selectedItem; private final HudState.Listener hudListener = new HudState.Listener() { @@ -37,8 +36,6 @@ public final class FeedActivity extends Activity { requestWindowFeature(Window.FEATURE_NO_TITLE); super.onCreate(savedInstanceState); - suppressionStore = new SuppressionStore(this); - cardScrollView = new CardScrollView(this); adapter = new FeedAdapter(this, makeWaitingCard()); cardScrollView.setAdapter(adapter); @@ -85,15 +82,11 @@ public final class FeedActivity extends Activity { String action = data.getStringExtra(MenuActivity.EXTRA_ACTION); if (cardId == null || action == null) return; - long now = System.currentTimeMillis() / 1000L; if ("DISMISS".equals(action)) { - suppressionStore.setSuppressed(cardId, now + (10L * 365L * 24L * 60L * 60L)); adapter.removeById(cardId); } else if ("SNOOZE_2H".equals(action)) { - suppressionStore.setSuppressed(cardId, now + 2L * 60L * 60L); adapter.removeById(cardId); } else if ("SNOOZE_24H".equals(action)) { - suppressionStore.setSuppressed(cardId, now + 24L * 60L * 60L); adapter.removeById(cardId); } else if ("SAVE".equals(action)) { Toast.makeText(this, "Saved", Toast.LENGTH_SHORT).show(); @@ -125,12 +118,10 @@ public final class FeedActivity extends Activity { List items = env.activeItems(); if (items.isEmpty()) return makeWaitingCard(); - long now = System.currentTimeMillis() / 1000L; ArrayList filtered = new ArrayList(); for (int i = 0; i < items.size(); i++) { FeedItem it = items.get(i); if (it == null) continue; - if (it.id != null && suppressionStore.isSuppressed(it.id, now)) continue; filtered.add(it); } if (filtered.isEmpty()) return makeWaitingCard(); diff --git a/IrisGlass/app/src/main/java/sh/nym/irisglass/MenuActivity.java b/IrisGlass/app/src/main/java/sh/nym/irisglass/MenuActivity.java index e4b1409..7fd2260 100644 --- a/IrisGlass/app/src/main/java/sh/nym/irisglass/MenuActivity.java +++ b/IrisGlass/app/src/main/java/sh/nym/irisglass/MenuActivity.java @@ -9,11 +9,9 @@ import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.Window; -import android.view.WindowManager; import java.util.HashSet; @@ -33,9 +31,10 @@ public final class MenuActivity extends Activity { private boolean isMenuClosed; private boolean preparePanelCalled; private boolean fromLiveCardVoice; + private boolean actionsReady; private String cardId; - private HashSet allowedActions = new HashSet(); + private final HashSet allowedActions = new HashSet(); public static Intent newIntent(Context context, FeedItem item) { Intent i = new Intent(context, MenuActivity.class); @@ -53,15 +52,6 @@ public final class MenuActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // Increase contrast so menu labels are readable over underlying cards. - try { - WindowManager.LayoutParams lp = getWindow().getAttributes(); - lp.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND; - lp.dimAmount = 0.65f; - getWindow().setAttributes(lp); - } catch (Throwable ignored) { - } - fromLiveCardVoice = getIntent().getBooleanExtra(LiveCard.EXTRA_FROM_LIVECARD_VOICE, false); if (fromLiveCardVoice) { getWindow().requestFeature(WindowUtils.FEATURE_VOICE_COMMANDS); @@ -74,6 +64,8 @@ public final class MenuActivity extends Activity { if (actions[i] != null) allowedActions.add(actions[i]); } } + actionsReady = true; + openMenu(); } @Override @@ -92,8 +84,7 @@ public final class MenuActivity extends Activity { @Override public boolean onCreatePanelMenu(int featureId, Menu menu) { if (isMyMenu(featureId)) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.feed_actions, menu); + getMenuInflater().inflate(R.menu.feed_actions, menu); return true; } return super.onCreatePanelMenu(featureId, menu); @@ -103,6 +94,9 @@ public final class MenuActivity extends Activity { public boolean onPreparePanel(int featureId, View view, Menu menu) { preparePanelCalled = true; if (isMyMenu(featureId)) { + if (!actionsReady) { + return false; + } // Enable only actions present on the tapped card. setOptionsMenuState(menu.findItem(R.id.action_dismiss), allowedActions.contains("DISMISS")); setOptionsMenuState(menu.findItem(R.id.action_snooze_2h), allowedActions.contains("SNOOZE_2H")); @@ -110,6 +104,8 @@ public final class MenuActivity extends Activity { setOptionsMenuState(menu.findItem(R.id.action_save), allowedActions.contains("SAVE")); // Always allow Back. setOptionsMenuState(menu.findItem(R.id.action_back), true); + // Don't reopen menu once we are finishing. This is necessary + // since voice menus reopen themselves while in focus. return !isMenuClosed; } return super.onPreparePanel(featureId, view, menu); @@ -129,7 +125,7 @@ public final class MenuActivity extends Activity { } // Post for proper options menu animation (per timer sample guidance). - handler.post(new Runnable() { + post(new Runnable() { @Override public void run() { Intent r = new Intent(); @@ -151,21 +147,43 @@ public final class MenuActivity extends Activity { } } + /** + * Posts a {@link Runnable} at the end of the message loop, overridable for testing. + */ + protected void post(Runnable runnable) { + handler.post(runnable); + } + + /** + * Opens the touch or voice menu iff all the conditions are satisfied. + */ private void openMenu() { - if (!attachedToWindow) return; - if (fromLiveCardVoice) { - if (preparePanelCalled) { - getWindow().invalidatePanelMenu(WindowUtils.FEATURE_VOICE_COMMANDS); + if (attachedToWindow && actionsReady) { + if (fromLiveCardVoice) { + if (preparePanelCalled) { + // Invalidates the previously prepared voice menu now that we can properly + // prepare it. + getWindow().invalidatePanelMenu(WindowUtils.FEATURE_VOICE_COMMANDS); + } + } else { + // Open the options menu for the touch flow. + openOptionsMenu(); } - } else { - openOptionsMenu(); } } + /** + * Returns {@code true} when the {@code featureId} belongs to the options menu or voice + * menu that are controlled by this menu activity. + */ private boolean isMyMenu(int featureId) { - return featureId == Window.FEATURE_OPTIONS_PANEL || featureId == WindowUtils.FEATURE_VOICE_COMMANDS; + return featureId == Window.FEATURE_OPTIONS_PANEL + || featureId == WindowUtils.FEATURE_VOICE_COMMANDS; } + /** + * Sets a {@code MenuItem} visible and enabled state. + */ private static void setOptionsMenuState(MenuItem menuItem, boolean enabled) { if (menuItem == null) return; menuItem.setVisible(enabled); diff --git a/IrisGlass/app/src/main/java/sh/nym/irisglass/SuppressionStore.java b/IrisGlass/app/src/main/java/sh/nym/irisglass/SuppressionStore.java deleted file mode 100644 index 948847d..0000000 --- a/IrisGlass/app/src/main/java/sh/nym/irisglass/SuppressionStore.java +++ /dev/null @@ -1,32 +0,0 @@ -package sh.nym.irisglass; - -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; - -public final class SuppressionStore { - private static final String KEY_PREFIX = "suppression_until_"; - - private final SharedPreferences prefs; - - public SuppressionStore(Context context) { - this.prefs = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()); - } - - public boolean isSuppressed(String id, long nowEpochSeconds) { - if (id == null) return false; - long until = prefs.getLong(KEY_PREFIX + id, 0L); - if (until <= 0L) return false; - if (until <= nowEpochSeconds) { - prefs.edit().remove(KEY_PREFIX + id).apply(); - return false; - } - return true; - } - - public void setSuppressed(String id, long untilEpochSeconds) { - if (id == null) return; - prefs.edit().putLong(KEY_PREFIX + id, untilEpochSeconds).apply(); - } -} - diff --git a/IrisGlass/app/src/main/res/values/themes.xml b/IrisGlass/app/src/main/res/values/themes.xml index 8ef4896..e838b11 100644 --- a/IrisGlass/app/src/main/res/values/themes.xml +++ b/IrisGlass/app/src/main/res/values/themes.xml @@ -8,13 +8,10 @@ -