From 782ede4254cf7cfae55f0e713c6754bc0f1759ea Mon Sep 17 00:00:00 2001 From: jahruz67 Date: Tue, 21 Jul 2026 17:35:42 -0700 Subject: [PATCH] Add dark mode support and improve language handling in SettingsActivity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Refactored color constants to support both light and dark modes. - Implemented a toggle for dark mode in SettingsActivity. - Updated language selection functionality to correctly display "Español". - Enhanced prayer management in ExtrasActivity with a detailed view. - Added new styles for dark mode in styles.xml. --- app/src/main/java/com/b | 478 ++++++++++++++++++ app/src/main/java/com/bibli | 424 ++++++++++++++++ .../com/bibliadiaria/app/MainActivity.java | 110 ++-- .../bibliadiaria/app/SettingsActivity.java | 26 +- .../com/bibliadiaria/app/UpdateManager.java | 15 +- app/src/main/res/values-night/styles.xml | 11 + 6 files changed, 1015 insertions(+), 49 deletions(-) create mode 100644 app/src/main/java/com/b create mode 100644 app/src/main/java/com/bibli create mode 100644 app/src/main/res/values-night/styles.xml diff --git a/app/src/main/java/com/b b/app/src/main/java/com/b new file mode 100644 index 0000000..ce91f27 --- /dev/null +++ b/app/src/main/java/com/b @@ -0,0 +1,478 @@ +package com.bibliadiaria.app; + +import android.app.Activity; +import android.app.AlertDialog; +import android.graphics.Color; +import android.graphics.Typeface; +import android.graphics.drawable.GradientDrawable; +import android.os.Build; +import android.os.Bundle; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.Switch; +import android.widget.TextView; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class SettingsActivity extends Activity { + // Light mode colors + private static final int COLOR_BG_LIGHT = Color.rgb(247, 248, 246); + private static final int COLOR_CARD_LIGHT = Color.WHITE; + private static final int COLOR_INK_LIGHT = Color.rgb(25, 31, 31); + private static final int COLOR_MUTED_LIGHT = Color.rgb(91, 99, 98); + private static final int COLOR_STROKE_LIGHT = Color.rgb(219, 226, 222); + private static final int COLOR_CARD_STROKE_LIGHT = Color.rgb(229, 232, 230); + + // Dark mode colors + private static final int COLOR_BG_DARK = Color.rgb(26, 28, 30); + private static final int COLOR_CARD_DARK = Color.rgb(38, 41, 44); + private static final int COLOR_INK_DARK = Color.rgb(224, 226, 219); + private static final int COLOR_MUTED_DARK = Color.rgb(158, 163, 160); + private static final int COLOR_STROKE_DARK = Color.rgb(48, 52, 55); + private static final int COLOR_CARD_STROKE_DARK = Color.rgb(58, 62, 65); + + private static final int COLOR_ACCENT = Color.rgb(0, 107, 90); + private static final int COLOR_WARM = Color.rgb(217, 75, 61); + + private ExecutorService executor; + private TextView statusText; + private TextView installedVersionText; + private TextView checkButton; + private TextView downloadButton; + private TextView voiceSubtitleText; + private TextView voiceButton; + private UpdateManager.UpdateInfo latestInfo; + private boolean isEnglish; + private boolean isDarkMode; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + isEnglish = UpdateManager.isEnglish(this); + isDarkMode = UpdateManager.isDarkMode(this); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + getWindow().setStatusBarColor(bg()); + getWindow().setNavigationBarColor(bg()); + } + + executor = Executors.newSingleThreadExecutor(); + setContentView(createScreen()); + updateInstalledVersionText(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (executor != null) { + executor.shutdownNow(); + } + } + + private int bg() { return isDarkMode ? COLOR_BG_DARK : COLOR_BG_LIGHT; } + private int card() { return isDarkMode ? COLOR_CARD_DARK : COLOR_CARD_LIGHT; } + private int ink() { return isDarkMode ? COLOR_INK_DARK : COLOR_INK_LIGHT; } + private int muted() { return isDarkMode ? COLOR_MUTED_DARK : COLOR_MUTED_LIGHT; } + private int stroke() { return isDarkMode ? COLOR_STROKE_DARK : COLOR_STROKE_LIGHT; } + private int cardStroke() { return isDarkMode ? COLOR_CARD_STROKE_DARK : COLOR_CARD_STROKE_LIGHT; } + + private View createScreen() { + FrameLayout root = new FrameLayout(this); + root.setBackgroundColor(bg()); + + ScrollView scrollView = new ScrollView(this); + scrollView.setFillViewport(true); + scrollView.setClipToPadding(false); + + LinearLayout content = new LinearLayout(this); + content.setOrientation(LinearLayout.VERTICAL); + content.setPadding(dp(20), dp(28), dp(20), dp(28)); + scrollView.addView(content, new ScrollView.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + )); + + content.addView(createTopBar()); + + TextView title = textView("Settings", 34, ink(), Typeface.BOLD); + title.setIncludeFontPadding(false); + LinearLayout.LayoutParams titleParams = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + titleParams.setMargins(0, dp(18), 0, dp(18)); + content.addView(title, titleParams); + + content.addView(createDarkModeCard()); + content.addView(createLanguageCard()); + content.addView(createVoiceCard()); + content.addView(createVersionCard()); + + checkButton = actionButton(isEnglish ? "Check now" : "Comprobar", COLOR_ACCENT, Color.WHITE); + checkButton.setOnClickListener(view -> checkForUpdate()); + LinearLayout.LayoutParams checkParams = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + dp(46) + ); + checkParams.setMargins(0, dp(16), 0, 0); + content.addView(checkButton, checkParams); + + downloadButton = actionButton("Download latest APK", COLOR_WARM, Color.WHITE); + downloadButton.setVisibility(View.GONE); + downloadButton.setOnClickListener(view -> { + String url = latestInfo == null ? UpdateManager.APK_URL : latestInfo.apkUrl; + UpdateManager.openDownload(this, url); + }); + LinearLayout.LayoutParams downloadParams = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + dp(46) + ); + downloadParams.setMargins(0, dp(10), 0, 0); + content.addView(downloadButton, downloadParams); + + root.addView(scrollView, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + )); + + root.setOnApplyWindowInsetsListener((view, insets) -> { + content.setPadding( + dp(20), + dp(28) + insets.getSystemWindowInsetTop(), + dp(20), + dp(28) + insets.getSystemWindowInsetBottom() + ); + return insets; + }); + root.requestApplyInsets(); + + return root; + } + + private View createTopBar() { + LinearLayout topBar = new LinearLayout(this); + topBar.setOrientation(LinearLayout.HORIZONTAL); + topBar.setGravity(Gravity.CENTER_VERTICAL); + + TextView label = textView("APP", 12, COLOR_ACCENT, Typeface.BOLD); + topBar.addView(label, new LinearLayout.LayoutParams( + 0, + ViewGroup.LayoutParams.WRAP_CONTENT, + 1f + )); + + TextView back = textView(isEnglish ? "Extras" : "Extras", 15, COLOR_ACCENT, Typeface.BOLD); + back.setGravity(Gravity.CENTER); + back.setPadding(dp(14), 0, dp(14), 0); + back.setBackground(roundedRect(card(), dp(8), stroke(), dp(1))); + back.setOnClickListener(view -> finish()); + topBar.addView(back, new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + dp(40) + )); + + return topBar; + } + + private View createDarkModeCard() { + LinearLayout card = createCard(); + card.setOrientation(LinearLayout.HORIZONTAL); + card.setGravity(Gravity.CENTER_VERTICAL); + + LinearLayout copy = new LinearLayout(this); + copy.setOrientation(LinearLayout.VERTICAL); + + TextView title = textView(isEnglish ? "Dark Mode" : "Modo oscuro", 20, ink(), Typeface.BOLD); + title.setIncludeFontPadding(false); + copy.addView(title); + + TextView subtitle = textView( + isEnglish ? "Use dark colors for the interface" : "Usar colores oscuros para la interfaz", + 14, muted(), Typeface.NORMAL); + LinearLayout.LayoutParams subtitleParams = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + subtitleParams.setMargins(0, dp(6), 0, 0); + copy.addView(subtitle, subtitleParams); + + card.addView(copy, new LinearLayout.LayoutParams( + 0, + ViewGroup.LayoutParams.WRAP_CONTENT, + 1f + )); + + final Switch darkToggle = new Switch(this); + darkToggle.setChecked(isDarkMode); + darkToggle.setOnCheckedChangeListener((buttonView, isChecked) -> { + UpdateManager.setDarkMode(this, isChecked); + isDarkMode = isChecked; + recreate(); + }); + card.addView(darkToggle, new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + )); + + return card; + } + + private View createLanguageCard() { + LinearLayout card = createCard(); + card.setOrientation(LinearLayout.HORIZONTAL); + card.setGravity(Gravity.CENTER_VERTICAL); + + LinearLayout copy = new LinearLayout(this); + copy.setOrientation(LinearLayout.VERTICAL); + + TextView title = textView("Language", 20, ink(), Typeface.BOLD); + title.setIncludeFontPadding(false); + copy.addView(title); + + TextView subtitle = textView("Select the language for daily readings", 14, muted(), Typeface.NORMAL); + LinearLayout.LayoutParams subtitleParams = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + subtitleParams.setMargins(0, dp(6), 0, 0); + copy.addView(subtitle, subtitleParams); + + card.addView(copy, new LinearLayout.LayoutParams( + 0, + ViewGroup.LayoutParams.WRAP_CONTENT, + 1f + )); + + TextView langToggle = actionButton(UpdateManager.isEnglish(this) ? "English" : "Español", COLOR_ACCENT, Color.WHITE); + langToggle.setOnClickListener(view -> { + boolean current = UpdateManager.isEnglish(this); + UpdateManager.setEnglish(this, !current); + langToggle.setText(!current ? "English" : "Español"); + updateVoiceCard(); + }); + card.addView(langToggle, new LinearLayout.LayoutParams(dp(100), dp(40))); + + return card; + } + + private View createVoiceCard() { + LinearLayout card = createCard(); + card.setOrientation(LinearLayout.VERTICAL); + + LinearLayout copy = new LinearLayout(this); + copy.setOrientation(LinearLayout.VERTICAL); + + TextView title = textView("Voice", 20, ink(), Typeface.BOLD); + title.setIncludeFontPadding(false); + copy.addView(title); + + voiceSubtitleText = textView("", 14, muted(), Typeface.NORMAL); + LinearLayout.LayoutParams subtitleParams = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + subtitleParams.setMargins(0, dp(6), 0, 0); + copy.addView(voiceSubtitleText, subtitleParams); + + card.addView(copy, new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + )); + + voiceButton = actionButton("", COLOR_ACCENT, Color.WHITE); + voiceButton.setOnClickListener(view -> showVoicePicker()); + LinearLayout.LayoutParams buttonParams = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + dp(42) + ); + buttonParams.setMargins(0, dp(12), 0, 0); + card.addView(voiceButton, buttonParams); + + updateVoiceCard(); + return card; + } + + private View createVersionCard() { + LinearLayout card = createCard(); + card.setOrientation(LinearLayout.VERTICAL); + + TextView title = textView("Version", 20, ink(), Typeface.BOLD); + title.setIncludeFontPadding(false); + card.addView(title); + + installedVersionText = textView("", 15, muted(), Typeface.BOLD); + LinearLayout.LayoutParams installedParams = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + installedParams.setMargins(0, dp(10), 0, 0); + card.addView(installedVersionText, installedParams); + + statusText = textView("Ready to check for updates.", 15, muted(), Typeface.NORMAL); + statusText.setLineSpacing(dp(3), 1.1f); + LinearLayout.LayoutParams statusParams = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + statusParams.setMargins(0, dp(8), 0, 0); + card.addView(statusText, statusParams); + + return card; + } + + private void checkForUpdate() { + if (executor == null || executor.isShutdown()) { + return; + } + + latestInfo = null; + downloadButton.setVisibility(View.GONE); + checkButton.setEnabled(false); + checkButton.setAlpha(0.62f); + statusText.setText("Checking for updates..."); + + executor.submit(() -> { + try { + UpdateManager.UpdateInfo info = UpdateManager.fetchLatestUpdate(this); + runOnUiThread(() -> showUpdateResult(info)); + } catch (Exception error) { + runOnUiThread(() -> showUpdateError(error)); + } + }); + } + + private void showUpdateResult(UpdateManager.UpdateInfo info) { + latestInfo = info; + checkButton.setEnabled(true); + checkButton.setAlpha(1f); + + if (info.updateAvailable) { + String version = info.latestVersionName.isEmpty() + ? String.valueOf(info.latestVersionCode) + : info.latestVersionName + " (" + info.latestVersionCode + ")"; + statusText.setText("Update available: " + version); + downloadButton.setVisibility(View.VISIBLE); + return; + } + + statusText.setText("You are on the latest build: " + + info.currentVersionName + " (" + info.currentVersionCode + ")"); + } + + private void showUpdateError(Exception error) { + checkButton.setEnabled(true); + checkButton.setAlpha(1f); + statusText.setText("Could not check for updates. " + cleanMessage(error)); + } + + private void showVoicePicker() { + boolean english = UpdateManager.isEnglish(this); + UpdateManager.VoiceOption[] options = UpdateManager.getTtsVoiceOptions(english); + String[] labels = new String[options.length]; + for (int index = 0; index < options.length; index++) { + labels[index] = options[index].label; + } + + int selectedIndex = UpdateManager.getTtsVoiceIndex( + english, + UpdateManager.getTtsVoice(this, english) + ); + + new AlertDialog.Builder(this) + .setTitle(english ? "English voice" : "Spanish voice") + .setSingleChoiceItems(labels, selectedIndex, (dialog, which) -> { + UpdateManager.setTtsVoice(this, english, options[which].id); + updateVoiceCard(); + dialog.dismiss(); + }) + .setNegativeButton("Cancel", null) + .show(); + } + + private void updateVoiceCard() { + if (voiceSubtitleText == null || voiceButton == null) { + return; + } + + boolean english = UpdateManager.isEnglish(this); + String voiceId = UpdateManager.getTtsVoice(this, english); + voiceSubtitleText.setText(english + ? "English voices are shown while English is selected" + : "Las voces en español se muestran mientras el español esté seleccionado"); + voiceButton.setText(UpdateManager.getTtsVoiceLabel(english, voiceId)); + } + + private void updateInstalledVersionText() { + try { + installedVersionText.setText( + (isEnglish ? "Installed: " : "Instalada: ") + + UpdateManager.getInstalledVersionName(this) + + " (" + UpdateManager.getInstalledVersionCode(this) + ")"); + } catch (Exception error) { + installedVersionText.setText(isEnglish ? "Installed version unavailable." : "Versión instalada no disponible."); + } + } + + private String cleanMessage(Exception error) { + String message = error.getMessage(); + if (message == null || message.trim().isEmpty()) { + return "Unknown error."; + } + return message.trim(); + } + + private LinearLayout createCard() { + LinearLayout card = new LinearLayout(this); + card.setPadding(dp(18), dp(16), dp(18), dp(16)); + card.setBackground(roundedRect(card(), dp(8), cardStroke(), dp(1))); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + card.setElevation(dp(1)); + } + + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + params.setMargins(0, 0, 0, dp(14)); + card.setLayoutParams(params); + return card; + } + + private TextView actionButton(String text, int backgroundColor, int textColor) { + TextView button = textView(text, 15, textColor, Typeface.BOLD); + button.setGravity(Gravity.CENTER); + button.setPadding(dp(14), 0, dp(14), 0); + button.setBackground(roundedRect(backgroundColor, dp(8), Color.TRANSPARENT, 0)); + return button; + } + + private TextView textView(String text, int sizeSp, int color, int style) { + TextView textView = new TextView(this); + textView.setText(text); + textView.setTextSize(sizeSp); + textView.setTextColor(color); + textView.setTypeface(Typeface.DEFAULT, style); + return textView; + } + + private GradientDrawable roundedRect(int color, int radius, int strokeColor, int strokeWidth) { + GradientDrawable drawable = new GradientDrawable(); + drawable.setShape(GradientDrawable.RECTANGLE); + drawable.setColor(color); + drawable.setCornerRadius(radius); + if (strokeWidth > 0) { + drawable.setStroke(strokeWidth, strokeColor); + } + return drawable; + } + + private int dp(int value) { + return Math.round(value * getResources().getDisplayMetrics().density); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bibli b/app/src/main/java/com/bibli new file mode 100644 index 0000000..d403d5d --- /dev/null +++ b/app/src/main/java/com/bibli @@ -0,0 +1,424 @@ +package com.bibliadiaria.app; + +import android.app.Activity; +import android.content.Intent; +import android.graphics.Color; +import android.graphics.Typeface; +import android.graphics.drawable.GradientDrawable; +import android.os.Build; +import android.os.Bundle; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +public class ExtrasActivity extends Activity { + // Light mode colors + private static final int COLOR_BG_LIGHT = Color.rgb(247, 248, 246); + private static final int COLOR_CARD_LIGHT = Color.WHITE; + private static final int COLOR_INK_LIGHT = Color.rgb(25, 31, 31); + private static final int COLOR_MUTED_LIGHT = Color.rgb(91, 99, 98); + private static final int COLOR_STROKE_LIGHT = Color.rgb(219, 226, 222); + private static final int COLOR_CARD_STROKE_LIGHT = Color.rgb(229, 232, 230); + + // Dark mode colors + private static final int COLOR_BG_DARK = Color.rgb(26, 28, 30); + private static final int COLOR_CARD_DARK = Color.rgb(38, 41, 44); + private static final int COLOR_INK_DARK = Color.rgb(224, 226, 219); + private static final int COLOR_MUTED_DARK = Color.rgb(158, 163, 160); + private static final int COLOR_STROKE_DARK = Color.rgb(48, 52, 55); + private static final int COLOR_CARD_STROKE_DARK = Color.rgb(58, 62, 65); + + private static final int COLOR_ACCENT = Color.rgb(0, 107, 90); + private static final int COLOR_WARM = Color.rgb(217, 75, 61); + + private boolean isEnglish; + private boolean isDarkMode; + + private static final String VENI_CREATOR_PRAYER = + "Ven, Esp\u00edritu Creador,\n" + + "visita las almas de tus fieles\n" + + "y llena de la gracia divina\n" + + "los corazones que t\u00fa mismo creaste.\n\n" + + "T\u00fa eres nuestro Consolador,\n" + + "don del Dios alt\u00edsimo,\n" + + "fuente viva, fuego, caridad\n" + + "y espiritual unci\u00f3n.\n\n" + + "T\u00fa derramas sobre nosotros\n" + + "los siete dones;\n" + + "t\u00fa eres el dedo de la diestra del Padre,\n" + + "promesa solemne del Padre,\n" + + "que pones en nuestros labios la palabra.\n\n" + + "Enciende tu luz en nuestras mentes,\n" + + "infunde tu amor en nuestros corazones\n" + + "y fortalece con tu fuerza constante\n" + + "la debilidad de nuestro cuerpo.\n\n" + + "Aleja de nosotros al enemigo,\n" + + "danos pronto la paz;\n" + + "siendo t\u00fa nuestro gu\u00eda,\n" + + "evitaremos todo mal.\n\n" + + "Por ti conozcamos al Padre,\n" + + "y tambi\u00e9n al Hijo;\n" + + "y que en ti, Esp\u00edritu de ambos,\n" + + "creamos en todo tiempo.\n\n" + + "Gloria a Dios Padre,\n" + + "y al Hijo que resucit\u00f3,\n" + + "y al Esp\u00edritu Consolador,\n" + + "por los siglos de los siglos.\n\n" + + "Am\u00e9n."; + + private static final String ANGELUS_PRAYER = + "El \u00c1ngel del Se\u00f1or anunci\u00f3 a Mar\u00eda.\n" + + "Y concibi\u00f3 por obra y gracia del Esp\u00edritu Santo.\n\n" + + "Dios te salve, Mar\u00eda, llena eres de gracia,\n" + + "el Se\u00f1or es contigo.\n" + + "Bendita t\u00fa eres entre todas las mujeres,\n" + + "y bendito es el fruto de tu vientre, Jes\u00fas.\n" + + "Santa Mar\u00eda, Madre de Dios,\n" + + "ruega por nosotros, pecadores,\n" + + "ahora y en la hora de nuestra muerte.\n" + + "Am\u00e9n.\n\n" + + "He aqu\u00ed la esclava del Se\u00f1or.\n" + + "H\u00e1gase en m\u00ed seg\u00fan tu palabra.\n\n" + + "Dios te salve, Mar\u00eda...\n\n" + + "Y el Verbo se hizo carne.\n" + + "Y habit\u00f3 entre nosotros.\n\n" + + "Dios te salve, Mar\u00eda...\n\n" + + "Ruega por nosotros, Santa Madre de Dios.\n" + + "Para que seamos dignos de alcanzar las promesas de Cristo.\n\n" + + "Infunde, Se\u00f1or, tu gracia en nuestros corazones,\n" + + "para que quienes hemos conocido por el anuncio del \u00c1ngel\n" + + "la encarnaci\u00f3n de tu Hijo Jesucristo,\n" + + "por su pasi\u00f3n y cruz seamos llevados\n" + + "a la gloria de su resurrecci\u00f3n.\n" + + "Por Jesucristo nuestro Se\u00f1or.\n\n" + + "Am\u00e9n."; + + private static final String SALVE_REGINA_PRAYER = + "Dios te salve, Reina y Madre de misericordia,\n" + + "vida, dulzura y esperanza nuestra;\n" + + "Dios te salve.\n\n" + + "A ti llamamos los desterrados hijos de Eva;\n" + + "a ti suspiramos, gimiendo y llorando\n" + + "en este valle de l\u00e1grimas.\n\n" + + "Ea, pues, Se\u00f1ora, abogada nuestra,\n" + + "vuelve a nosotros esos tus ojos misericordiosos;\n" + + "y despu\u00e9s de este destierro,\n" + + "mu\u00e9stranos a Jes\u00fas,\n" + + "fruto bendito de tu vientre.\n\n" + + "\u00a1Oh clemente, oh piadosa,\n" + + "oh dulce Virgen Mar\u00eda!\n\n" + + "Ruega por nosotros, Santa Madre de Dios,\n" + + "para que seamos dignos de alcanzar\n" + + "las promesas de nuestro Se\u00f1or Jesucristo.\n\n" + + "Am\u00e9n."; + + private static final String MEMORARE_PRAYER = + "Acordaos, oh piados\u00edsima Virgen Mar\u00eda,\n" + + "que jam\u00e1s se ha o\u00eddo decir\n" + + "que ninguno de los que han acudido a vuestra protecci\u00f3n,\n" + + "implorado vuestra asistencia\n" + + "y reclamado vuestro socorro,\n" + + "haya sido abandonado de vos.\n\n" + + "Animado con esta confianza,\n" + + "a vos tambi\u00e9n acudo,\n" + + "oh Madre, Virgen de las v\u00edrgenes;\n" + + "y aunque gimiendo bajo el peso de mis pecados,\n" + + "me atrevo a comparecer ante vuestra presencia soberana.\n\n" + + "No desech\u00e9is, oh Madre de Dios,\n" + + "mis humildes s\u00faplicas;\n" + + "antes bien, escuchadlas y acogedlas benignamente.\n\n" + + "Am\u00e9n."; + + private static final PrayerItem[] PRAYERS = { + new PrayerItem("Ven, Esp\u00edritu Creador", "Veni Creator Spiritus", VENI_CREATOR_PRAYER), + new PrayerItem("\u00c1ngelus", "Oraci\u00f3n de la Encarnaci\u00f3n", ANGELUS_PRAYER), + new PrayerItem("Salve Regina", "Dios te salve, Reina", SALVE_REGINA_PRAYER), + new PrayerItem("Acordaos", "Memorare", MEMORARE_PRAYER) + }; + + private ScrollView scrollView; + private LinearLayout listContainer; + private LinearLayout detailContainer; + private TextView titleText; + private PrayerItem selectedPrayer = PRAYERS[0]; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + isEnglish = UpdateManager.isEnglish(this); + isDarkMode = UpdateManager.isDarkMode(this); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + getWindow().setStatusBarColor(bg()); + getWindow().setNavigationBarColor(bg()); + } + + setContentView(createScreen()); + } + + @Override + protected void onResume() { + super.onResume(); + boolean currentLanguage = UpdateManager.isEnglish(this); + boolean currentDark = UpdateManager.isDarkMode(this); + if (currentLanguage != isEnglish || currentDark != isDarkMode) { + isEnglish = currentLanguage; + isDarkMode = currentDark; + recreate(); + } + } + + @Override + public void onBackPressed() { + if (detailContainer != null && detailContainer.getVisibility() == View.VISIBLE) { + showPrayerList(); + return; + } + super.onBackPressed(); + } + + private int bg() { return isDarkMode ? COLOR_BG_DARK : COLOR_BG_LIGHT; } + private int card() { return isDarkMode ? COLOR_CARD_DARK : COLOR_CARD_LIGHT; } + private int ink() { return isDarkMode ? COLOR_INK_DARK : COLOR_INK_LIGHT; } + private int muted() { return isDarkMode ? COLOR_MUTED_DARK : COLOR_MUTED_LIGHT; } + private int stroke() { return isDarkMode ? COLOR_STROKE_DARK : COLOR_STROKE_LIGHT; } + private int cardStroke() { return isDarkMode ? COLOR_CARD_STROKE_DARK : COLOR_CARD_STROKE_LIGHT; } + + private View createScreen() { + FrameLayout root = new FrameLayout(this); + root.setBackgroundColor(bg()); + + scrollView = new ScrollView(this); + scrollView.setFillViewport(true); + scrollView.setClipToPadding(false); + + LinearLayout content = new LinearLayout(this); + content.setOrientation(LinearLayout.VERTICAL); + content.setPadding(dp(20), dp(28), dp(20), dp(28)); + scrollView.addView(content, new ScrollView.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + )); + + content.addView(createTopBar()); + + titleText = textView(isEnglish ? "Extras" : "Extras", 34, ink(), Typeface.BOLD); + titleText.setIncludeFontPadding(false); + LinearLayout.LayoutParams titleParams = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + titleParams.setMargins(0, dp(18), 0, dp(18)); + content.addView(titleText, titleParams); + + listContainer = new LinearLayout(this); + listContainer.setOrientation(LinearLayout.VERTICAL); + for (PrayerItem prayer : PRAYERS) { + listContainer.addView(createPrayerCard(prayer)); + } + content.addView(listContainer, new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + )); + + detailContainer = new LinearLayout(this); + detailContainer.setOrientation(LinearLayout.VERTICAL); + detailContainer.setVisibility(View.GONE); + content.addView(detailContainer, new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + )); + + root.addView(scrollView, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + )); + + root.setOnApplyWindowInsetsListener((view, insets) -> { + content.setPadding( + dp(20), + dp(28) + insets.getSystemWindowInsetTop(), + dp(20), + dp(28) + insets.getSystemWindowInsetBottom() + ); + return insets; + }); + root.requestApplyInsets(); + + return root; + } + + private View createTopBar() { + LinearLayout topBar = new LinearLayout(this); + topBar.setOrientation(LinearLayout.HORIZONTAL); + topBar.setGravity(Gravity.CENTER_VERTICAL); + + TextView label = textView(isEnglish ? "PRAYERS" : "ORACIONES", 12, COLOR_ACCENT, Typeface.BOLD); + topBar.addView(label, new LinearLayout.LayoutParams( + 0, + ViewGroup.LayoutParams.WRAP_CONTENT, + 1f + )); + + TextView settings = textView(isEnglish ? "Settings" : "Ajustes", 15, COLOR_ACCENT, Typeface.BOLD); + settings.setGravity(Gravity.CENTER); + settings.setPadding(dp(14), 0, dp(14), 0); + settings.setBackground(roundedRect(card(), dp(8), stroke(), dp(1))); + settings.setOnClickListener(view -> startActivity(new Intent(this, SettingsActivity.class))); + LinearLayout.LayoutParams settingsParams = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + dp(40) + ); + settingsParams.setMargins(0, 0, dp(8), 0); + topBar.addView(settings, settingsParams); + + TextView home = textView(isEnglish ? "Home" : "Inicio", 15, COLOR_ACCENT, Typeface.BOLD); + home.setGravity(Gravity.CENTER); + home.setPadding(dp(14), 0, dp(14), 0); + home.setBackground(roundedRect(card(), dp(8), stroke(), dp(1))); + home.setOnClickListener(view -> finish()); + topBar.addView(home, new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + dp(40) + )); + + return topBar; + } + + private View createPrayerCard(PrayerItem prayer) { + LinearLayout card = new LinearLayout(this); + card.setOrientation(LinearLayout.HORIZONTAL); + card.setGravity(Gravity.CENTER_VERTICAL); + card.setPadding(dp(18), dp(16), dp(16), dp(16)); + card.setBackground(roundedRect(card(), dp(8), cardStroke(), dp(1))); + card.setOnClickListener(view -> showPrayerDetail(prayer)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + card.setElevation(dp(1)); + } + + LinearLayout copy = new LinearLayout(this); + copy.setOrientation(LinearLayout.VERTICAL); + + TextView title = textView(prayer.title, 20, ink(), Typeface.BOLD); + title.setIncludeFontPadding(false); + copy.addView(title); + + TextView subtitle = textView(prayer.subtitle, 14, muted(), Typeface.BOLD); + LinearLayout.LayoutParams subtitleParams = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + subtitleParams.setMargins(0, dp(6), 0, 0); + copy.addView(subtitle, subtitleParams); + + card.addView(copy, new LinearLayout.LayoutParams( + 0, + ViewGroup.LayoutParams.WRAP_CONTENT, + 1f + )); + + TextView arrow = textView(">", 22, COLOR_ACCENT, Typeface.BOLD); + arrow.setGravity(Gravity.CENTER); + card.addView(arrow, new LinearLayout.LayoutParams( + dp(28), + ViewGroup.LayoutParams.WRAP_CONTENT + )); + + LinearLayout.LayoutParams cardParams = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + cardParams.setMargins(0, 0, 0, dp(12)); + card.setLayoutParams(cardParams); + return card; + } + + private void renderPrayerDetail() { + detailContainer.removeAllViews(); + + TextView subtitle = textView(selectedPrayer.subtitle, 14, COLOR_WARM, Typeface.BOLD); + detailContainer.addView(subtitle); + + TextView prayer = textView(selectedPrayer.body, 21, ink(), Typeface.NORMAL); + prayer.setLineSpacing(dp(5), 1.14f); + LinearLayout.LayoutParams prayerParams = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + prayerParams.setMargins(0, dp(16), 0, dp(18)); + detailContainer.addView(prayer, prayerParams); + + TextView backToList = textView(isEnglish ? "Back to extras" : "Volver a extras", 15, COLOR_ACCENT, Typeface.BOLD); + backToList.setGravity(Gravity.CENTER); + backToList.setPadding(dp(14), 0, dp(14), 0); + backToList.setBackground(roundedRect(card(), dp(8), stroke(), dp(1))); + backToList.setOnClickListener(view -> showPrayerList()); + detailContainer.addView(backToList, new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + dp(42) + )); + } + + private void showPrayerDetail(PrayerItem prayer) { + selectedPrayer = prayer; + titleText.setText(prayer.title); + renderPrayerDetail(); + listContainer.setVisibility(View.GONE); + detailContainer.setVisibility(View.VISIBLE); + scrollToTop(); + } + + private void showPrayerList() { + titleText.setText(isEnglish ? "Extras" : "Extras"); + detailContainer.setVisibility(View.GONE); + listContainer.setVisibility(View.VISIBLE); + scrollToTop(); + } + + private void scrollToTop() { + if (scrollView != null) { + scrollView.post(() -> scrollView.smoothScrollTo(0, 0)); + } + } + + private TextView textView(String text, int sizeSp, int color, int style) { + TextView textView = new TextView(this); + textView.setText(text); + textView.setTextSize(sizeSp); + textView.setTextColor(color); + textView.setTypeface(Typeface.DEFAULT, style); + return textView; + } + + private GradientDrawable roundedRect(int color, int radius, int strokeColor, int strokeWidth) { + GradientDrawable drawable = new GradientDrawable(); + drawable.setShape(GradientDrawable.RECTANGLE); + drawable.setColor(color); + drawable.setCornerRadius(radius); + if (strokeWidth > 0) { + drawable.setStroke(strokeWidth, strokeColor); + } + return drawable; + } + + private int dp(int value) { + return Math.round(value * getResources().getDisplayMetrics().density); + } + + private static class PrayerItem { + final String title; + final String subtitle; + final String body; + + PrayerItem(String title, String subtitle, String body) { + this.title = title; + this.subtitle = subtitle; + this.body = body; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bibliadiaria/app/MainActivity.java b/app/src/main/java/com/bibliadiaria/app/MainActivity.java index a872f0e..369c881 100644 --- a/app/src/main/java/com/bibliadiaria/app/MainActivity.java +++ b/app/src/main/java/com/bibliadiaria/app/MainActivity.java @@ -50,10 +50,29 @@ import java.util.concurrent.Executors; import org.json.JSONObject; public class MainActivity extends Activity { - private static final int COLOR_BACKGROUND = Color.rgb(247, 248, 246); - private static final int COLOR_CARD = Color.WHITE; - private static final int COLOR_INK = Color.rgb(25, 31, 31); - private static final int COLOR_MUTED = Color.rgb(91, 99, 98); + // Light mode colors + private static final int COLOR_BACKGROUND_LIGHT = Color.rgb(247, 248, 246); + private static final int COLOR_CARD_LIGHT = Color.WHITE; + private static final int COLOR_INK_LIGHT = Color.rgb(25, 31, 31); + private static final int COLOR_MUTED_LIGHT = Color.rgb(91, 99, 98); + private static final int COLOR_STROKE_LIGHT = Color.rgb(229, 232, 230); + private static final int COLOR_CHIP_BG_LIGHT = Color.rgb(219, 226, 222); + private static final int COLOR_SPEAKER_BG_LIGHT = Color.rgb(229, 244, 240); + private static final int COLOR_REFERENCE_BG_LIGHT = Color.rgb(229, 244, 240); + private static final int COLOR_TODAY_BG_LIGHT = Color.rgb(255, 239, 236); + + // Dark mode colors + private static final int COLOR_BACKGROUND_DARK = Color.rgb(26, 28, 30); + private static final int COLOR_CARD_DARK = Color.rgb(38, 41, 44); + private static final int COLOR_INK_DARK = Color.rgb(224, 226, 219); + private static final int COLOR_MUTED_DARK = Color.rgb(158, 163, 160); + private static final int COLOR_STROKE_DARK = Color.rgb(58, 62, 65); + private static final int COLOR_CHIP_BG_DARK = Color.rgb(48, 52, 55); + private static final int COLOR_SPEAKER_BG_DARK = Color.rgb(45, 65, 60); + private static final int COLOR_REFERENCE_BG_DARK = Color.rgb(45, 65, 60); + private static final int COLOR_TODAY_BG_DARK = Color.rgb(75, 50, 48); + + // Shared colors (no change between modes) private static final int COLOR_ACCENT = Color.rgb(0, 107, 90); private static final int COLOR_WARM = Color.rgb(217, 75, 61); private static final int COLOR_HOLIDAY_BG = Color.rgb(255, 246, 229); @@ -120,19 +139,21 @@ public class MainActivity extends Activity { private int ttsGeneration; private float readingFontSp; private boolean isEnglish; + private boolean isDarkMode; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - getWindow().setStatusBarColor(COLOR_BACKGROUND); - getWindow().setNavigationBarColor(COLOR_BACKGROUND); + getWindow().setStatusBarColor(bg()); + getWindow().setNavigationBarColor(bg()); } preferences = getSharedPreferences("preferencias_lectura", MODE_PRIVATE); readingFontSp = preferences.getFloat("tamano_letra", 20f); isEnglish = preferences.getBoolean("is_english", false); + isDarkMode = preferences.getBoolean("dark_mode", false); selectedDateCalendar = startOfDay(Calendar.getInstance()); visibleMonthCalendar = startOfMonth(selectedDateCalendar); executor = Executors.newSingleThreadExecutor(); @@ -163,15 +184,28 @@ public class MainActivity extends Activity { protected void onResume() { super.onResume(); boolean currentLanguage = UpdateManager.isEnglish(this); - if (currentLanguage != isEnglish) { + boolean currentDark = UpdateManager.isDarkMode(this); + if (currentLanguage != isEnglish || currentDark != isDarkMode) { isEnglish = currentLanguage; + isDarkMode = currentDark; recreate(); } } + // --- Dark mode aware color helpers --- + private int bg() { return isDarkMode ? COLOR_BACKGROUND_DARK : COLOR_BACKGROUND_LIGHT; } + private int card() { return isDarkMode ? COLOR_CARD_DARK : COLOR_CARD_LIGHT; } + private int ink() { return isDarkMode ? COLOR_INK_DARK : COLOR_INK_LIGHT; } + private int muted() { return isDarkMode ? COLOR_MUTED_DARK : COLOR_MUTED_LIGHT; } + private int stroke() { return isDarkMode ? COLOR_STROKE_DARK : COLOR_STROKE_LIGHT; } + private int chipBg() { return isDarkMode ? COLOR_CHIP_BG_DARK : COLOR_CHIP_BG_LIGHT; } + private int speakerBg() { return isDarkMode ? COLOR_SPEAKER_BG_DARK : COLOR_SPEAKER_BG_LIGHT; } + private int referenceBg() { return isDarkMode ? COLOR_REFERENCE_BG_DARK : COLOR_REFERENCE_BG_LIGHT; } + private int todayBg() { return isDarkMode ? COLOR_TODAY_BG_DARK : COLOR_TODAY_BG_LIGHT; } + private View createScreen() { FrameLayout root = new FrameLayout(this); - root.setBackgroundColor(COLOR_BACKGROUND); + root.setBackgroundColor(bg()); ScrollView scrollView = new ScrollView(this); scrollView.setFillViewport(true); @@ -195,7 +229,7 @@ public class MainActivity extends Activity { ViewGroup.LayoutParams.WRAP_CONTENT )); - sourceText = textView(isEnglish ? "Source: Vatican News" : "Fuente: Vatican News", 13, COLOR_MUTED, Typeface.NORMAL); + sourceText = textView(isEnglish ? "Source: Vatican News" : "Fuente: Vatican News", 13, muted(), Typeface.NORMAL); LinearLayout.LayoutParams sourceParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT @@ -268,7 +302,7 @@ public class MainActivity extends Activity { topRow.addView(createDateChip()); header.addView(topRow); - TextView title = textView(isEnglish ? "Word of the day" : "Palabra del día", 34, COLOR_INK, Typeface.BOLD); + TextView title = textView(isEnglish ? "Word of the day" : "Palabra del día", 34, ink(), Typeface.BOLD); title.setIncludeFontPadding(false); LinearLayout.LayoutParams titleParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, @@ -292,7 +326,7 @@ public class MainActivity extends Activity { calendarParams.setMargins(0, dp(2), 0, dp(14)); header.addView(calendarPanel, calendarParams); - dateText = textView("", 16, COLOR_MUTED, Typeface.BOLD); + dateText = textView("", 16, muted(), Typeface.BOLD); header.addView(dateText); holidayTagsRow = new LinearLayout(this); @@ -306,7 +340,7 @@ public class MainActivity extends Activity { tagsParams.setMargins(0, dp(8), 0, 0); header.addView(holidayTagsRow, tagsParams); - liturgyText = textView("", 16, COLOR_INK, Typeface.NORMAL); + liturgyText = textView("", 16, ink(), Typeface.NORMAL); liturgyText.setLineSpacing(0, 1.12f); LinearLayout.LayoutParams liturgyParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, @@ -339,7 +373,7 @@ public class MainActivity extends Activity { chip.setOrientation(LinearLayout.HORIZONTAL); chip.setGravity(Gravity.CENTER_VERTICAL); chip.setPadding(dp(12), dp(8), dp(10), dp(8)); - chip.setBackground(roundedRect(Color.WHITE, dp(8), Color.rgb(219, 226, 222), dp(1))); + chip.setBackground(roundedRect(card(), dp(8), chipBg(), dp(1))); chip.setContentDescription(isEnglish ? "Choose date" : "Elegir fecha"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { chip.setElevation(dp(1)); @@ -350,11 +384,11 @@ public class MainActivity extends Activity { labelGroup.setOrientation(LinearLayout.VERTICAL); labelGroup.setGravity(Gravity.CENTER_VERTICAL); - dateChipPrimary = textView("", 20, COLOR_INK, Typeface.BOLD); + dateChipPrimary = textView("", 20, ink(), Typeface.BOLD); dateChipPrimary.setIncludeFontPadding(false); labelGroup.addView(dateChipPrimary); - dateChipSecondary = textView("", 11, COLOR_MUTED, Typeface.BOLD); + dateChipSecondary = textView("", 11, muted(), Typeface.BOLD); dateChipSecondary.setIncludeFontPadding(false); labelGroup.addView(dateChipSecondary); @@ -378,7 +412,7 @@ public class MainActivity extends Activity { panel.setOrientation(LinearLayout.VERTICAL); panel.setPadding(dp(14), dp(12), dp(14), dp(14)); panel.setVisibility(View.GONE); - panel.setBackground(roundedRect(Color.WHITE, dp(8), Color.rgb(219, 226, 222), dp(1))); + panel.setBackground(roundedRect(card(), dp(8), chipBg(), dp(1))); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { panel.setElevation(dp(4)); } @@ -389,7 +423,7 @@ public class MainActivity extends Activity { monthRow.addView(calendarNavButton("<", view -> changeVisibleMonth(-1))); - calendarMonthText = textView("", 18, COLOR_INK, Typeface.BOLD); + calendarMonthText = textView("", 18, ink(), Typeface.BOLD); calendarMonthText.setGravity(Gravity.CENTER); monthRow.addView(calendarMonthText, new LinearLayout.LayoutParams( 0, @@ -418,7 +452,7 @@ public class MainActivity extends Activity { weekdays.setOrientation(LinearLayout.HORIZONTAL); String[] labels = {"L", "M", "X", "J", "V", "S", "D"}; for (String label : labels) { - TextView dayLabel = textView(label, 12, COLOR_MUTED, Typeface.BOLD); + TextView dayLabel = textView(label, 12, muted(), Typeface.BOLD); dayLabel.setGravity(Gravity.CENTER); weekdays.addView(dayLabel, new LinearLayout.LayoutParams( 0, @@ -443,7 +477,7 @@ public class MainActivity extends Activity { private TextView calendarNavButton(String text, View.OnClickListener listener) { TextView button = textView(text, 22, COLOR_ACCENT, Typeface.BOLD); button.setGravity(Gravity.CENTER); - button.setBackground(roundedRect(Color.rgb(229, 244, 240), dp(8), Color.TRANSPARENT, 0)); + button.setBackground(roundedRect(speakerBg(), dp(8), Color.TRANSPARENT, 0)); button.setOnClickListener(listener); button.setContentDescription(text.equals("<") ? (isEnglish ? "Previous month" : "Mes anterior") : (isEnglish ? "Next month" : "Mes siguiente")); return button; @@ -453,7 +487,7 @@ public class MainActivity extends Activity { TextView button = textView(text, 13, COLOR_ACCENT, Typeface.BOLD); button.setGravity(Gravity.CENTER); button.setPadding(dp(8), 0, dp(8), 0); - button.setBackground(roundedRect(Color.rgb(229, 244, 240), dp(8), Color.TRANSPARENT, 0)); + button.setBackground(roundedRect(speakerBg(), dp(8), Color.TRANSPARENT, 0)); button.setOnClickListener(listener); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( @@ -512,7 +546,7 @@ public class MainActivity extends Activity { Calendar today = startOfDay(Calendar.getInstance()); for (int cell = 0; cell < 42; cell++) { - TextView dayCell = textView("", 16, COLOR_INK, Typeface.BOLD); + TextView dayCell = textView("", 16, ink(), Typeface.BOLD); dayCell.setGravity(Gravity.CENTER); dayCell.setIncludeFontPadding(false); @@ -540,7 +574,7 @@ public class MainActivity extends Activity { dayCell.setBackground(roundedRect(COLOR_HOLIDAY_BG, dp(8), COLOR_HOLIDAY_BORDER, dp(1))); } else if (isSameDay(cellDate, today)) { dayCell.setTextColor(COLOR_WARM); - dayCell.setBackground(roundedRect(Color.rgb(255, 239, 236), dp(8), Color.TRANSPARENT, 0)); + dayCell.setBackground(roundedRect(todayBg(), dp(8), Color.TRANSPARENT, 0)); } else { dayCell.setBackground(roundedRect(Color.TRANSPARENT, dp(8), Color.TRANSPARENT, 0)); } @@ -983,7 +1017,7 @@ public class MainActivity extends Activity { statusBlock = new LinearLayout(this); statusBlock.setOrientation(LinearLayout.VERTICAL); statusBlock.setPadding(dp(18), dp(16), dp(18), dp(16)); - statusBlock.setBackground(roundedRect(COLOR_CARD, dp(8), Color.TRANSPARENT, 0)); + statusBlock.setBackground(roundedRect(card(), dp(8), Color.TRANSPARENT, 0)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { statusBlock.setElevation(dp(1)); } @@ -997,7 +1031,7 @@ public class MainActivity extends Activity { progressParams.setMargins(0, 0, dp(12), 0); row.addView(progressBar, progressParams); - statusText = textView(isEnglish ? "Loading today's Word..." : "Cargando la Palabra de hoy...", 16, COLOR_INK, Typeface.NORMAL); + statusText = textView(isEnglish ? "Loading today's Word..." : "Cargando la Palabra de hoy...", 16, ink(), Typeface.NORMAL); statusText.setLineSpacing(0, 1.16f); row.addView(statusText, new LinearLayout.LayoutParams( 0, @@ -1035,7 +1069,7 @@ public class MainActivity extends Activity { panel.setOrientation(LinearLayout.VERTICAL); panel.setPadding(dp(16), dp(14), dp(16), dp(14)); panel.setVisibility(View.GONE); - panel.setBackground(roundedRect(Color.WHITE, dp(8), Color.rgb(223, 229, 226), dp(1))); + panel.setBackground(roundedRect(card(), dp(8), stroke(), dp(1))); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { panel.setElevation(dp(8)); } @@ -1043,10 +1077,10 @@ public class MainActivity extends Activity { LinearLayout labelRow = new LinearLayout(this); labelRow.setGravity(Gravity.CENTER_VERTICAL); - TextView label = textView(isEnglish ? "Font size" : "Tamaño de letra", 15, COLOR_INK, Typeface.BOLD); + TextView label = textView(isEnglish ? "Font size" : "Tamaño de letra", 15, ink(), Typeface.BOLD); labelRow.addView(label, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)); - fontValueText = textView(Math.round(readingFontSp) + " sp", 14, COLOR_MUTED, Typeface.BOLD); + fontValueText = textView(Math.round(readingFontSp) + " sp", 14, muted(), Typeface.BOLD); labelRow.addView(fontValueText); panel.addView(labelRow); @@ -1380,7 +1414,7 @@ public class MainActivity extends Activity { button.setTextColor(COLOR_ACCENT); button.setTextSize(14); button.setAllCaps(false); - button.setBackground(roundedRect(Color.WHITE, dp(8), Color.rgb(219, 226, 222), dp(1))); + button.setBackground(roundedRect(card(), dp(8), stroke(), dp(1))); return button; } @@ -1674,7 +1708,7 @@ public class MainActivity extends Activity { row.setOrientation(LinearLayout.HORIZONTAL); row.setGravity(Gravity.CENTER_VERTICAL); - TextView titleText = textView(title, titleSizeSp, COLOR_INK, Typeface.BOLD); + TextView titleText = textView(title, titleSizeSp, ink(), Typeface.BOLD); titleText.setIncludeFontPadding(false); row.addView(titleText, new LinearLayout.LayoutParams( 0, @@ -1686,7 +1720,7 @@ public class MainActivity extends Activity { speakerButton.setImageResource(R.drawable.ic_speaker_24); speakerButton.setColorFilter(COLOR_ACCENT); speakerButton.setPadding(dp(8), dp(8), dp(8), dp(8)); - speakerButton.setBackground(roundedRect(Color.rgb(229, 244, 240), dp(8), Color.TRANSPARENT, 0)); + speakerButton.setBackground(roundedRect(speakerBg(), dp(8), Color.TRANSPARENT, 0)); speakerButton.setContentDescription((isEnglish ? "Listen to " : "Escuchar ") + title); speakerButton.setOnClickListener(view -> startTtsConversionForText(speechText, controls)); @@ -1697,7 +1731,7 @@ public class MainActivity extends Activity { } private TtsControls createTtsControls() { - TextView status = textView("", 14, COLOR_MUTED, Typeface.BOLD); + TextView status = textView("", 14, muted(), Typeface.BOLD); status.setVisibility(View.GONE); LinearLayout playerPanel = new LinearLayout(this); @@ -1738,7 +1772,7 @@ public class MainActivity extends Activity { seekParams.setMargins(dp(8), 0, dp(8), 0); playerPanel.addView(seekBar, seekParams); - TextView timeText = textView("0:00", 13, COLOR_MUTED, Typeface.BOLD); + TextView timeText = textView("0:00", 13, muted(), Typeface.BOLD); timeText.setGravity(Gravity.CENTER_VERTICAL); playerPanel.addView(timeText, new LinearLayout.LayoutParams( dp(86), @@ -1797,7 +1831,7 @@ public class MainActivity extends Activity { addTtsControlsToCard(card, ttsControls); if (!section.introduction.isEmpty()) { - TextView introText = textView(section.introduction, 16, COLOR_MUTED, Typeface.BOLD); + TextView introText = textView(section.introduction, 16, muted(), Typeface.BOLD); introText.setLineSpacing(0, 1.14f); resizableTextViews.add(introText); card.addView(introText); @@ -1806,7 +1840,7 @@ public class MainActivity extends Activity { if (!section.reference.isEmpty()) { TextView referenceText = textView(section.reference, 15, COLOR_ACCENT, Typeface.BOLD); referenceText.setGravity(Gravity.CENTER_VERTICAL); - referenceText.setBackground(roundedRect(Color.rgb(229, 244, 240), dp(8), Color.TRANSPARENT, 0)); + referenceText.setBackground(roundedRect(referenceBg(), dp(8), Color.TRANSPARENT, 0)); referenceText.setPadding(dp(10), dp(6), dp(10), dp(6)); LinearLayout.LayoutParams referenceParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, @@ -1816,7 +1850,7 @@ public class MainActivity extends Activity { card.addView(referenceText, referenceParams); } - TextView bodyText = textView(section.body, Math.round(readingFontSp), COLOR_INK, Typeface.NORMAL); + TextView bodyText = textView(section.body, Math.round(readingFontSp), ink(), Typeface.NORMAL); bodyText.setLineSpacing(dp(4), 1.16f); resizableTextViews.add(bodyText); card.addView(bodyText); @@ -1841,7 +1875,7 @@ public class MainActivity extends Activity { )); addTtsControlsToCard(card, ttsControls); - TextView body = textView(section.body, Math.round(readingFontSp), COLOR_INK, Typeface.NORMAL); + TextView body = textView(section.body, Math.round(readingFontSp), ink(), Typeface.NORMAL); body.setLineSpacing(dp(4), 1.16f); LinearLayout.LayoutParams bodyParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, @@ -1858,7 +1892,7 @@ public class MainActivity extends Activity { LinearLayout card = new LinearLayout(this); card.setOrientation(LinearLayout.VERTICAL); card.setPadding(padding, padding, padding, padding); - card.setBackground(roundedRect(COLOR_CARD, dp(8), Color.rgb(229, 232, 230), dp(1))); + card.setBackground(roundedRect(card(), dp(8), stroke(), dp(1))); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { card.setElevation(dp(1)); } @@ -2416,4 +2450,4 @@ public class MainActivity extends Activity { return lines; } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/bibliadiaria/app/SettingsActivity.java b/app/src/main/java/com/bibliadiaria/app/SettingsActivity.java index 60dffbe..d6f0565 100644 --- a/app/src/main/java/com/bibliadiaria/app/SettingsActivity.java +++ b/app/src/main/java/com/bibliadiaria/app/SettingsActivity.java @@ -20,10 +20,20 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class SettingsActivity extends Activity { - private static final int COLOR_BACKGROUND = Color.rgb(247, 248, 246); - private static final int COLOR_CARD = Color.WHITE; - private static final int COLOR_INK = Color.rgb(25, 31, 31); - private static final int COLOR_MUTED = Color.rgb(91, 99, 98); + // Light mode colors + private static final int COLOR_BG_LIGHT = Color.rgb(247, 248, 246); + private static final int COLOR_CARD_LIGHT = Color.WHITE; + private static final int COLOR_INK_LIGHT = Color.rgb(25, 31, 31); + private static final int COLOR_MUTED_LIGHT = Color.rgb(91, 99, 98); + private static final int COLOR_STROKE_LIGHT = Color.rgb(219, 226, 222); + + // Dark mode colors + private static final int COLOR_BG_DARK = Color.rgb(26, 28, 30); + private static final int COLOR_CARD_DARK = Color.rgb(38, 41, 44); + private static final int COLOR_INK_DARK = Color.rgb(224, 226, 219); + private static final int COLOR_MUTED_DARK = Color.rgb(158, 163, 160); + private static final int COLOR_STROKE_DARK = Color.rgb(58, 62, 65); + private static final int COLOR_ACCENT = Color.rgb(0, 107, 90); private static final int COLOR_WARM = Color.rgb(217, 75, 61); @@ -183,11 +193,11 @@ public class SettingsActivity extends Activity { 1f )); - TextView langToggle = actionButton(UpdateManager.isEnglish(this) ? "English" : "Español", COLOR_ACCENT, Color.WHITE); + TextView langToggle = actionButton(UpdateManager.isEnglish(this) ? "English" : "Español", COLOR_ACCENT, Color.WHITE); langToggle.setOnClickListener(view -> { boolean current = UpdateManager.isEnglish(this); UpdateManager.setEnglish(this, !current); - langToggle.setText(!current ? "English" : "Español"); + langToggle.setText(!current ? "English" : "Español"); updateVoiceCard(); }); card.addView(langToggle, new LinearLayout.LayoutParams(dp(100), dp(40))); @@ -338,7 +348,7 @@ public class SettingsActivity extends Activity { String voiceId = UpdateManager.getTtsVoice(this, english); voiceSubtitleText.setText(english ? "English voices are shown while English is selected" - : "Las voces en español se muestran mientras el español esté seleccionado"); + : "Las voces en español se muestran mientras el español esté seleccionado"); voiceButton.setText(UpdateManager.getTtsVoiceLabel(english, voiceId)); } @@ -349,7 +359,7 @@ public class SettingsActivity extends Activity { + UpdateManager.getInstalledVersionName(this) + " (" + UpdateManager.getInstalledVersionCode(this) + ")"); } catch (Exception error) { - installedVersionText.setText(isEnglish ? "Installed version unavailable." : "Versión instalada no disponible."); + installedVersionText.setText(isEnglish ? "Installed version unavailable." : "Versión instalada no disponible."); } } diff --git a/app/src/main/java/com/bibliadiaria/app/UpdateManager.java b/app/src/main/java/com/bibliadiaria/app/UpdateManager.java index 7d6e18d..7a651cf 100644 --- a/app/src/main/java/com/bibliadiaria/app/UpdateManager.java +++ b/app/src/main/java/com/bibliadiaria/app/UpdateManager.java @@ -24,6 +24,7 @@ final class UpdateManager { static final String PREFS_NAME = "preferencias_lectura"; static final String KEY_AUTO_UPDATES = "auto_updates_enabled"; static final String KEY_IS_ENGLISH = "is_english"; + static final String KEY_DARK_MODE = "dark_mode"; static final String KEY_TTS_VOICE_ENGLISH = "tts_voice_english"; static final String KEY_TTS_VOICE_SPANISH = "tts_voice_spanish"; static final String DEFAULT_TTS_VOICE_ENGLISH = "af_heart"; @@ -89,6 +90,14 @@ final class UpdateManager { preferences(context).edit().putBoolean(KEY_IS_ENGLISH, enabled).apply(); } + static boolean isDarkMode(Context context) { + return preferences(context).getBoolean(KEY_DARK_MODE, false); + } + + static void setDarkMode(Context context, boolean enabled) { + preferences(context).edit().putBoolean(KEY_DARK_MODE, enabled).apply(); + } + static VoiceOption[] getTtsVoiceOptions(boolean isEnglish) { return isEnglish ? ENGLISH_VOICES : SPANISH_VOICES; } @@ -207,10 +216,10 @@ final class UpdateManager { boolean english = isEnglish(activity); new AlertDialog.Builder(activity) - .setTitle(english ? "Update available" : "Actualización disponible") - .setMessage((english ? "Version " : "Versión ") + version + (english ? " is ready to download." : " está lista para descargar.")) + .setTitle(english ? "Update available" : "Actualización disponible") + .setMessage((english ? "Version " : "Versión ") + version + (english ? " is ready to download." : " está lista para descargar.")) .setPositiveButton(english ? "Download" : "Descargar", (dialog, which) -> openDownload(activity, info.apkUrl)) - .setNegativeButton(english ? "Later" : "Después", null) + .setNegativeButton(english ? "Later" : "Después", null) .show(); } diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..c9feb14 --- /dev/null +++ b/app/src/main/res/values-night/styles.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file