Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2276,6 +2276,15 @@ private String queryDisplayName(Uri uri) {
return null;
}

/** Show a Snackbar whose visible time scales with the message length (reading time). */
private void showImportSnackbar(CharSequence text) {
View v = getView();
if (v != null) {
Snackbar.make(v, text,
org.iiab.controller.util.SnackbarDuration.millisForText(text.toString())).show();
}
}

private void importBackupSafely(Uri sourceUri) {
isImporting = true;
updateDynamicButtons();
Expand Down Expand Up @@ -2334,10 +2343,11 @@ private void importBackupSafely(Uri sourceUri) {
if (getActivity() != null) {
getActivity().runOnUiThread(() -> {
isImporting = false;
stopImportSpinner(); // stop the braille spinner; rejection ends the import
updateDynamicButtons();
btnImportBackup.setEnabled(true);
btnImportBackup.setText(getString(R.string.install_btn_import_backup));
Snackbar.make(getView(), errMsg, Snackbar.LENGTH_LONG).show();
showImportSnackbar(getString(errMsg));
});
}
return;
Expand All @@ -2346,12 +2356,12 @@ private void importBackupSafely(Uri sourceUri) {
// user (a future version will validate silently). See docs/ROOTFS_MANIFEST.md.
if (okNoManifest && getActivity() != null) {
getActivity().runOnUiThread(() ->
Snackbar.make(getView(), R.string.install_warn_manifest_missing, Snackbar.LENGTH_LONG).show());
showImportSnackbar(getString(R.string.install_warn_manifest_missing)));
}
// Transparency: an app-made (device) backup carries no integrity checksum.
if (okNoChecksum && getActivity() != null) {
getActivity().runOnUiThread(() ->
Snackbar.make(getView(), R.string.install_warn_no_checksum, Snackbar.LENGTH_LONG).show());
showImportSnackbar(getString(R.string.install_warn_no_checksum)));
}

if (getActivity() != null) {
Expand All @@ -2362,7 +2372,7 @@ private void importBackupSafely(Uri sourceUri) {
btnImportBackup.setText(getString(R.string.install_btn_import_backup));
selectedBackupFile = fileName;
updateDynamicButtons();
Snackbar.make(getView(), getString(R.string.install_msg_import_success), Snackbar.LENGTH_LONG).show();
showImportSnackbar(getString(R.string.install_msg_import_success));
});
}
} catch (Exception e) {
Expand All @@ -2373,7 +2383,7 @@ private void importBackupSafely(Uri sourceUri) {
updateDynamicButtons();
btnImportBackup.setEnabled(true);
btnImportBackup.setText(getString(R.string.install_btn_import_backup));
Snackbar.make(getView(), getString(R.string.install_msg_import_failed, e.getMessage()), Snackbar.LENGTH_LONG).show();
showImportSnackbar(getString(R.string.install_msg_import_failed, e.getMessage()));
});
}
} finally {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* ============================================================================
* Name : SnackbarDuration.java
* Author : AppDevForAll
* Copyright : Copyright (c) 2026 AppDevForAll
* Description : Reading-time based Snackbar duration (longer text shows longer).
* ============================================================================
*/
package org.iiab.controller.util;

/**
* Snackbar has only SHORT (~1.5s) / LONG (~2.75s). Long messages get cut off.
* This scales the duration with the message length using an average reading
* speed (~200 wpm ≈ 300ms/word) plus a reaction margin, clamped so it never
* flashes too briefly nor lingers forever. Pure JVM (unit-tested).
*/
public final class SnackbarDuration {

private SnackbarDuration() {}

static final int BASE_MS = 1500; // fixed reaction/append time
static final int PER_WORD_MS = 320; // ~200 wpm reading speed + margin
static final int MIN_MS = 3000; // floor (~ LENGTH_LONG)
static final int MAX_MS = 10000; // cap so it doesn't linger

/** Duration in ms appropriate for reading {@code text}. */
public static int millisForText(String text) {
if (text == null) return MIN_MS;
String t = text.trim();
if (t.isEmpty()) return MIN_MS;
int words = t.split("\\s+").length;
long ms = BASE_MS + (long) words * PER_WORD_MS;
if (ms < MIN_MS) ms = MIN_MS;
if (ms > MAX_MS) ms = MAX_MS;
return (int) ms;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.iiab.controller.util;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import org.junit.Test;

/** Pure-JVM tests for reading-time Snackbar duration. */
public class SnackbarDurationTest {

@Test public void nullOrBlankIsFloor() {
assertEquals(SnackbarDuration.MIN_MS, SnackbarDuration.millisForText(null));
assertEquals(SnackbarDuration.MIN_MS, SnackbarDuration.millisForText(" "));
}

@Test public void shortTextStaysAtFloor() {
assertEquals(SnackbarDuration.MIN_MS, SnackbarDuration.millisForText("Done"));
}

@Test public void longerTextScalesUp() {
int many = SnackbarDuration.millisForText(
"This backup was built for a different processor architecture and cannot run on this device");
assertTrue("expected > floor, got " + many, many > SnackbarDuration.MIN_MS);
assertTrue(many <= SnackbarDuration.MAX_MS);
}

@Test public void veryLongTextIsCapped() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 200; i++) sb.append("word ");
assertEquals(SnackbarDuration.MAX_MS, SnackbarDuration.millisForText(sb.toString()));
}
}
Loading