diff --git a/app/src/main/java/com/almothafar/simplebatterynotifier/service/BatteryRateTracker.java b/app/src/main/java/com/almothafar/simplebatterynotifier/service/BatteryRateTracker.java index 9845a94..604d571 100644 --- a/app/src/main/java/com/almothafar/simplebatterynotifier/service/BatteryRateTracker.java +++ b/app/src/main/java/com/almothafar/simplebatterynotifier/service/BatteryRateTracker.java @@ -61,7 +61,7 @@ public final class BatteryRateTracker { static final int MAX_PLAUSIBLE_RATE_PPH = 500; // The "red / high drain" limit shared with the fast-drain alert (#109): default and accepted range. - // MIN/MAX must match the slider bounds (android:min/android:max) in pref_notification.xml; they are + // MIN/MAX must match the slider bounds (android:min/android:max) in pref_alerts.xml; they are // enforced when the preference is read, so an out-of-range stored value can't skew the red line. public static final int DEFAULT_DRAIN_LIMIT_PPH = 20; public static final int MIN_DRAIN_LIMIT_PPH = 5; @@ -363,7 +363,7 @@ public static int getDrainLimitPercentPerHour(final Context context) { /** * Clamps a stored drain limit to the accepted range, so a corrupt or out-of-range preference value * can't skew the red line here or the fast-drain trigger in #109. The bounds mirror the slider's - * {@code android:min}/{@code android:max} in {@code pref_notification.xml}. Pure so it is unit-testable. + * {@code android:min}/{@code android:max} in {@code pref_alerts.xml}. Pure so it is unit-testable. * * @param stored the raw persisted limit in %/h * diff --git a/app/src/main/java/com/almothafar/simplebatterynotifier/service/NotificationService.java b/app/src/main/java/com/almothafar/simplebatterynotifier/service/NotificationService.java index 571574e..8f3e3b1 100644 --- a/app/src/main/java/com/almothafar/simplebatterynotifier/service/NotificationService.java +++ b/app/src/main/java/com/almothafar/simplebatterynotifier/service/NotificationService.java @@ -87,7 +87,7 @@ public final class NotificationService { private static final String ZEN_MODE = "zen_mode"; private static final int ZEN_MODE_IMPORTANT_INTERRUPTIONS = 1; - // Charge-connected notification style (values persisted by the ListPreference in pref_notification.xml). + // Charge-connected notification style (values persisted by the ListPreference in pref_alerts.xml). // Toast is the default so plugging in stays low-clutter (issue #122). static final String CHARGE_STYLE_TOAST = "toast"; static final String CHARGE_STYLE_NOTIFICATION = "notification"; @@ -842,7 +842,7 @@ private static boolean isVibrationEnabled(final Context context, final SharedPre * @return true if alerts are allowed at the current time */ private static boolean isWithinNotificationWindow(final Context context, final SharedPreferences prefs) { - // Default ON to match the toggle's XML default (pref_time_settings.xml). Reading false here let a + // Default ON to match the toggle's XML default (pref_behaviour.xml). Reading false here let a // fresh install that never opened Time Settings alert around the clock, so quiet hours silently // did nothing (issue #111). Now quiet hours apply by default (06:30–23:30). final boolean limitedTime = prefs.getBoolean(context.getString(R.string._pref_key_notifications_time_range), true); diff --git a/app/src/main/java/com/almothafar/simplebatterynotifier/ui/AboutDialog.java b/app/src/main/java/com/almothafar/simplebatterynotifier/ui/AboutDialog.java new file mode 100644 index 0000000..cf5a33b --- /dev/null +++ b/app/src/main/java/com/almothafar/simplebatterynotifier/ui/AboutDialog.java @@ -0,0 +1,79 @@ +package com.almothafar.simplebatterynotifier.ui; + +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.util.Log; +import android.view.View; +import android.widget.TextView; +import android.widget.Toast; + +import com.almothafar.simplebatterynotifier.R; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +/** + * Shared "About the app" dialog and app-version lookup. + *

+ * Used from two entry points: the main screen's overflow menu (#136) and the version footer at + * the bottom of the Settings root screen (#113), so both stay one implementation. + */ +public final class AboutDialog { + + private static final String TAG = "AboutDialog"; + + private AboutDialog() { + } + + /** + * Show the "About the app" dialog: what the app is, the current version, developer credit, + * the open-source license, and a short accuracy/warranty note. + * + * @param activity The host activity + */ + public static void show(final Activity activity) { + final View content = activity.getLayoutInflater().inflate(R.layout.dialog_about, null); + + final TextView versionView = content.findViewById(R.id.aboutVersion); + versionView.setText(activity.getString(R.string.about_version, appVersionName(activity))); + + final TextView developerView = content.findViewById(R.id.aboutDeveloper); + developerView.setText(activity.getString(R.string.about_developer, activity.getString(R.string.developer_name))); + + new MaterialAlertDialogBuilder(activity) + .setView(content) + .setPositiveButton(android.R.string.ok, null) + .setNeutralButton(R.string.about_view_github, (dialog, which) -> openProjectPage(activity)) + .show(); + } + + /** + * The app's version name (e.g. "2.0.71"), or an empty string if it can't be read. + * + * @param context Any context of this app + */ + public static String appVersionName(final Context context) { + try { + return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName; + } catch (PackageManager.NameNotFoundException e) { + // The app can always resolve its own package, so this is effectively unreachable; + // log it rather than swallow silently, and degrade to an empty version string. + Log.w(TAG, "Unable to read app version name", e); + return ""; + } + } + + /** + * Open the project's GitHub page in a browser. + */ + private static void openProjectPage(final Activity activity) { + final Uri uri = Uri.parse(activity.getString(R.string.about_github_url)); + try { + activity.startActivity(new Intent(Intent.ACTION_VIEW, uri)); + } catch (ActivityNotFoundException e) { + Toast.makeText(activity, R.string.no_browser_found, Toast.LENGTH_SHORT).show(); + } + } +} diff --git a/app/src/main/java/com/almothafar/simplebatterynotifier/ui/MainActivity.java b/app/src/main/java/com/almothafar/simplebatterynotifier/ui/MainActivity.java index 13465b0..6933e26 100644 --- a/app/src/main/java/com/almothafar/simplebatterynotifier/ui/MainActivity.java +++ b/app/src/main/java/com/almothafar/simplebatterynotifier/ui/MainActivity.java @@ -15,7 +15,6 @@ import android.util.Log; import android.view.Menu; import android.view.MenuItem; -import android.view.View; import android.widget.TextView; import android.widget.Toast; import com.almothafar.simplebatterynotifier.ui.fragment.BatteryDetailsFragment; @@ -105,7 +104,7 @@ public boolean onOptionsItemSelected(final MenuItem item) { return true; } if (id == R.id.action_about) { - showAboutDialog(); + AboutDialog.show(this); return true; } return super.onOptionsItemSelected(item); @@ -463,38 +462,6 @@ private void openBatteryInsights() { startActivity(intent); } - /** - * Show the "About the app" dialog: what the app is, the current version, developer credit, - * the open-source license, and a short accuracy/warranty note. - */ - private void showAboutDialog() { - final View content = getLayoutInflater().inflate(R.layout.dialog_about, null); - - final TextView versionView = content.findViewById(R.id.aboutVersion); - versionView.setText(getString(R.string.about_version, appVersionName())); - - final TextView developerView = content.findViewById(R.id.aboutDeveloper); - developerView.setText(getString(R.string.about_developer, getString(R.string.developer_name))); - - new MaterialAlertDialogBuilder(this) - .setView(content) - .setPositiveButton(android.R.string.ok, null) - .setNeutralButton(R.string.about_view_github, (dialog, which) -> openProjectPage()) - .show(); - } - - /** - * Open the project's GitHub page in a browser. - */ - private void openProjectPage() { - final Uri uri = Uri.parse(getString(R.string.about_github_url)); - try { - startActivity(new Intent(Intent.ACTION_VIEW, uri)); - } catch (ActivityNotFoundException e) { - Toast.makeText(this, R.string.no_browser_found, Toast.LENGTH_SHORT).show(); - } - } - /** * Show the feedback chooser: report on GitHub, or email the developer. */ @@ -534,7 +501,7 @@ private void openGitHubIssues() { private void emailDeveloper() { final Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", getString(R.string.feedback_support_email), null)); - intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.feedback_email_subject, appVersionName())); + intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.feedback_email_subject, AboutDialog.appVersionName(this))); intent.putExtra(Intent.EXTRA_TEXT, deviceInfoBlock()); try { startActivity(intent); @@ -543,26 +510,12 @@ private void emailDeveloper() { } } - /** - * The app's version name (e.g. "2.0.71"), or an empty string if it can't be read. - */ - private String appVersionName() { - try { - return getPackageManager().getPackageInfo(getPackageName(), 0).versionName; - } catch (PackageManager.NameNotFoundException e) { - // The app can always resolve its own package, so this is effectively unreachable; - // log it rather than swallow silently, and degrade to an empty version string. - Log.w(TAG, "Unable to read app version name", e); - return ""; - } - } - /** * A short diagnostic block appended to a feedback email so reports carry the essentials. */ private String deviceInfoBlock() { return "\n\n---\n" - + "App: " + appVersionName() + "\n" + + "App: " + AboutDialog.appVersionName(this) + "\n" + "Device: " + Build.MANUFACTURER + " " + Build.MODEL + "\n" + "Android: " + Build.VERSION.RELEASE + " (API " + Build.VERSION.SDK_INT + ")"; } diff --git a/app/src/main/java/com/almothafar/simplebatterynotifier/ui/SettingsActivity.java b/app/src/main/java/com/almothafar/simplebatterynotifier/ui/SettingsActivity.java index ac4d28c..685f6c0 100644 --- a/app/src/main/java/com/almothafar/simplebatterynotifier/ui/SettingsActivity.java +++ b/app/src/main/java/com/almothafar/simplebatterynotifier/ui/SettingsActivity.java @@ -139,6 +139,17 @@ public static class HeaderFragment extends PreferenceFragmentCompat { @Override public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) { setPreferencesFromResource(R.xml.pref_headers_root, rootKey); + + // Version footer (#113): show the running app version at the bottom of the root + // screen; tapping it opens the shared About dialog (same one as the main menu, #136). + final Preference about = findPreference(getString(R.string._pref_key_about)); + if (nonNull(about)) { + about.setTitle(getString(R.string.about_version, AboutDialog.appVersionName(requireContext()))); + about.setOnPreferenceClickListener(pref -> { + AboutDialog.show(requireActivity()); + return true; + }); + } } } } diff --git a/app/src/main/java/com/almothafar/simplebatterynotifier/ui/fragment/GenericPreferenceFragment.java b/app/src/main/java/com/almothafar/simplebatterynotifier/ui/fragment/GenericPreferenceFragment.java index 44c4f74..7f06712 100644 --- a/app/src/main/java/com/almothafar/simplebatterynotifier/ui/fragment/GenericPreferenceFragment.java +++ b/app/src/main/java/com/almothafar/simplebatterynotifier/ui/fragment/GenericPreferenceFragment.java @@ -120,11 +120,11 @@ public void onCreatePreferences(final Bundle savedInstanceState, final String ro if (category.equals(getString(R.string.pref_category_general))) { setPreferencesFromResource(R.xml.pref_general, rootKey); configureLanguagePreference(); - } else if (category.equals(getString(R.string.pref_category_notifications))) { - setPreferencesFromResource(R.xml.pref_notification, rootKey); + } else if (category.equals(getString(R.string.pref_category_alerts))) { + setPreferencesFromResource(R.xml.pref_alerts, rootKey); configureTemperatureThreshold(); - } else if (category.equals(getString(R.string.pref_category_time_settings))) { - setPreferencesFromResource(R.xml.pref_time_settings, rootKey); + } else if (category.equals(getString(R.string.pref_category_behaviour))) { + setPreferencesFromResource(R.xml.pref_behaviour, rootKey); } } } diff --git a/app/src/main/res/layout/dialog_about.xml b/app/src/main/res/layout/dialog_about.xml index 436e8ea..681b5c6 100644 --- a/app/src/main/res/layout/dialog_about.xml +++ b/app/src/main/res/layout/dialog_about.xml @@ -1,5 +1,5 @@ - + مدخل يو اس بي شاحن لاسلكي + عام - التنبيهات والأصوات - إعدادات الوقت - إعدادات التطبيق العامة، ويمكنك تحديد المستويات - إعدادات كيفية وصول التنبيهات، وتحديدها، والأصوات - إعدادات الوقت للتنبيهات + التنبيهات + سلوك التنبيهات + لغة التطبيق، ووحدة الحرارة، وإشعار الحالة + ما الذي يُنبَّه له — مستويات البطارية، اكتمال الشحن، الحرارة، الاستهلاك السريع، الشحن + كيف تُصدر التنبيهات صوتاً وكيف تظهر ومتى يُسمح بها — وضع الصامت، الاهتزاز، ساعات الهدوء الرنين صامت @@ -62,19 +63,21 @@ وحدة الحرارة إطفاء تشغيل - التنبيهات ثابتة - التنبيهات غير ثابتة - تنبيه ثابت - صوت التنبيه سيسمع حتى لو في وضعية الصامت - وضعية الصامت لن يتم تجاهلها ولن تسمع التنبيهات في وضعية صامت - تفعيل وضعية صامت - التنبيه الصوتي في أي وقت من اليوم - التنبيه الصوتي محدود بفترة محددة في اليوم - وقت محدد للتنبيهات - وقت البداية للتنبيه - وقت النهاية للتنبيه - التنبيه بالرجاج مفعل - التنبيه بالرجاج معطل + لا يمكن إزالة إشعارات التنبيه بالسحب + يمكن إزالة إشعارات التنبيه بشكل طبيعي + إشعارات ثابتة + + تُصدر التنبيهات صوتاً حتى عندما يكون الهاتف في وضع الصامت أو عدم الإزعاج + تبقى التنبيهات صامتة عندما يكون الهاتف في وضع الصامت أو عدم الإزعاج + كتم التنبيهات في وضع الصامت + يمكن أن تُصدر التنبيهات صوتاً في أي وقت من اليوم + تُصدر التنبيهات صوتاً فقط بين الوقتين أدناه، وتبقى صامتة خارجهما + ساعات الهدوء + التنبيهات مسموحة من + التنبيهات مسموحة حتى + + التنبيه بالرجاج معطل + التنبيه بالرجاج مفعل صوت المستوى الحرج صوت مستوى التنبيه صوت تنبيه اكتمال الشحن @@ -247,6 +250,8 @@ برنامج حر ومفتوح المصدر، مرخَّص بموجب رخصة Apache 2.0. قراءات البطارية وأرقام الصحة والشحن مصدرها جهازك وهي تقديرية — اعتبرها إرشادية لا قياسات دقيقة. يُقدَّم هذا التطبيق «كما هو» دون أي ضمان من أي نوع. عرض على GitHub + + اضغط لمزيد من المعلومات حول التطبيق اللغة @@ -255,11 +260,14 @@ العرض - حدود مستوى البطارية - إعدادات أخرى - إعدادات التنبيهات + إشعار الحالة + مستويات البطارية البطارية الممتلئة - مستوى التحذير - الحرارة - مستوى البطارية الحرج + تنبيه التحذير + ارتفاع الحرارة + التنبيه الحرج + الشحن + الصوت والاهتزاز + ساعات الهدوء + المظهر diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 39afde8..9929cb2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -43,12 +43,13 @@ USB Port Wireless Charger + General - Notifications & Sounds - Notification Time Settings - General app settings, and you can set the levels here - Settings for how to get your notifications, what you want to notify, and sounds - When to get notifications + Alerts + Notification Behaviour + App language, temperature unit, and the status notification + What to be notified about — battery levels, full charge, temperature, fast drain, charging + How alerts sound and appear, and when they are allowed — silent mode, vibration, quiet hours Ringtone Silent @@ -65,17 +66,20 @@ Temperature Unit OFF ON - Notification is Sticky - Notification is not Sticky - Sticky Notification - Notification sound will play even if phone in silent mode - Silent mode will not be ignored, and you will NOT hear notification sound if phone in silent mode - Apply Silent Mode - Notification sounds any moment of a Day - Notification sounds only within specific time in a Day - Limited Notifications Time Range - Notify Start Time - Notify End Time + Alert notifications can\'t be swiped away + Alert notifications can be dismissed normally + Sticky notifications + + Alert sounds play even while the phone is in silent mode or Do Not Disturb + Alerts stay silent while the phone is in silent mode or Do Not Disturb + Mute alerts in silent mode + + Alerts can sound at any time of day + Alerts sound only between the times below, and stay quiet outside them + Quiet hours + Alerts allowed from + Alerts allowed until Notifications Vibration Disabled Notifications Vibration Enabled Critical Level Sound @@ -259,6 +263,8 @@ Battery readings, health and charging figures come from your device and are estimates — treat them as guidance, not exact measurements. This app is provided “as is”, without warranty of any kind. View on GitHub https://github.com/almothafar/SimpleBatteryNotifier + + Tap for more about this app Language @@ -270,21 +276,26 @@ Display - Battery Level Thresholds - Other Settings - Notification Settings + Status Notification + Battery Levels Full Battery - Warning Level - Temperature - Critical Battery Level + Warning Alert + High Temperature + Critical Alert + Charging + Sound & Vibration + Quiet Hours + Appearance category pref_category_general - pref_category_notifications - pref_category_time_settings + pref_category_alerts + pref_category_behaviour + + key_about key_warn_battery_level key_critical_battery_level diff --git a/app/src/main/res/xml/pref_notification.xml b/app/src/main/res/xml/pref_alerts.xml similarity index 79% rename from app/src/main/res/xml/pref_notification.xml rename to app/src/main/res/xml/pref_alerts.xml index fcc6113..0b56121 100644 --- a/app/src/main/res/xml/pref_notification.xml +++ b/app/src/main/res/xml/pref_alerts.xml @@ -1,37 +1,72 @@ + + - + + + + + + + + + + + + @@ -60,30 +95,6 @@ - - - - - - - - @@ -168,47 +179,21 @@ app:adjustable="true" app:iconSpaceReserved="false" /> - - + - - - - - diff --git a/app/src/main/res/xml/pref_behaviour.xml b/app/src/main/res/xml/pref_behaviour.xml new file mode 100644 index 0000000..67c4e11 --- /dev/null +++ b/app/src/main/res/xml/pref_behaviour.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/xml/pref_general.xml b/app/src/main/res/xml/pref_general.xml index 8b8fad9..92642eb 100644 --- a/app/src/main/res/xml/pref_general.xml +++ b/app/src/main/res/xml/pref_general.xml @@ -31,36 +31,19 @@ + - - - - - - diff --git a/app/src/main/res/xml/pref_headers.xml b/app/src/main/res/xml/pref_headers.xml deleted file mode 100644 index 434de15..0000000 --- a/app/src/main/res/xml/pref_headers.xml +++ /dev/null @@ -1,30 +0,0 @@ - - -

- -
- -
- -
- -
- -
- - diff --git a/app/src/main/res/xml/pref_headers_legacy.xml b/app/src/main/res/xml/pref_headers_legacy.xml deleted file mode 100644 index c1a57cf..0000000 --- a/app/src/main/res/xml/pref_headers_legacy.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/xml/pref_headers_root.xml b/app/src/main/res/xml/pref_headers_root.xml index ab0f9e2..cdeb573 100644 --- a/app/src/main/res/xml/pref_headers_root.xml +++ b/app/src/main/res/xml/pref_headers_root.xml @@ -15,24 +15,32 @@ + android:value="@string/pref_category_alerts" /> + android:value="@string/pref_category_behaviour" /> + + + diff --git a/app/src/main/res/xml/pref_time_settings.xml b/app/src/main/res/xml/pref_time_settings.xml deleted file mode 100644 index 0c6d0c0..0000000 --- a/app/src/main/res/xml/pref_time_settings.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - -