├── .github ├── FUNDING.yml └── images │ ├── Screenshot_20240809_113408_RomanDigital.png │ ├── Screenshot_20240809_161416_RomanDigital.png │ ├── Screenshot_20240809_205721_RomanDigital.png │ ├── Screenshot_20240910_174429_One_UI_Home_scaled.jpg │ ├── Screenshot_20241008_143851_One_UI_Home_scaled.jpg │ ├── Screenshot_20250219_115336_One_UI_Home_scaled.jpg │ ├── Screenshot_20250219_120411_One_UI_Home_scaled.jpg │ ├── Torn_Screenshot_20240913_171548_One_UI_Home.png │ ├── clearpixel.gif │ └── get-the-latest-apk-on-github.png ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── net │ │ └── diffengine │ │ └── romandigitalclock │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── net │ │ │ └── diffengine │ │ │ └── romandigitalclock │ │ │ ├── AboutActivity.java │ │ │ ├── AppClass.java │ │ │ ├── BootCompletedBroadcastReceiver.java │ │ │ ├── ColorDialogPreference.java │ │ │ ├── ColorSeekBarView.java │ │ │ ├── MainActivity.java │ │ │ ├── SettingsActivity.java │ │ │ ├── SettingsButtonBarFragment.java │ │ │ ├── TimeDisplayWidget.java │ │ │ ├── TimeDisplayWidgetConfigActivity.java │ │ │ └── romantime.java │ ├── rd_launcher-playstore.png │ └── res │ │ ├── color │ │ ├── ab_switch_thumb.xml │ │ └── ab_switch_track.xml │ │ ├── drawable-v21 │ │ ├── app_widget_background.xml │ │ ├── app_widget_inner_view_background.xml │ │ ├── appwidget_bkgnd_0.xml │ │ ├── appwidget_bkgnd_10.xml │ │ ├── appwidget_bkgnd_100.xml │ │ ├── appwidget_bkgnd_20.xml │ │ ├── appwidget_bkgnd_30.xml │ │ ├── appwidget_bkgnd_40.xml │ │ ├── appwidget_bkgnd_50.xml │ │ ├── appwidget_bkgnd_60.xml │ │ ├── appwidget_bkgnd_70.xml │ │ ├── appwidget_bkgnd_80.xml │ │ └── appwidget_bkgnd_90.xml │ │ ├── drawable │ │ ├── ic_info_outline_24.xml │ │ ├── ic_settings_24dp.xml │ │ ├── romandigital_3x1_appwidget_preview.png │ │ └── seekbar_track_material.xml │ │ ├── layout │ │ ├── a_b_switch_layout.xml │ │ ├── about_activity.xml │ │ ├── activity_main.xml │ │ ├── activity_time_display_widget_config.xml │ │ ├── color_dialog_layout.xml │ │ ├── color_seekbar_layout.xml │ │ ├── fragment_settings_button_bar.xml │ │ ├── separator_layout.xml │ │ ├── settings_activity.xml │ │ ├── time_display_widget.xml │ │ ├── time_display_widget_hi_label.xml │ │ └── time_display_widget_lo_label.xml │ │ ├── menu │ │ └── main_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── rd_launcher.xml │ │ └── rd_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── rd_launcher.webp │ │ ├── rd_launcher_foreground.webp │ │ └── rd_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── rd_launcher.webp │ │ ├── rd_launcher_foreground.webp │ │ └── rd_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── rd_launcher.webp │ │ ├── rd_launcher_foreground.webp │ │ └── rd_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── rd_launcher.webp │ │ ├── rd_launcher_foreground.webp │ │ └── rd_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── rd_launcher.webp │ │ ├── rd_launcher_foreground.webp │ │ └── rd_launcher_round.webp │ │ ├── values-night-v31 │ │ └── themes.xml │ │ ├── values-night │ │ ├── colors.xml │ │ └── themes.xml │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-v31 │ │ ├── styles.xml │ │ └── themes.xml │ │ ├── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── rd_launcher_background.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ └── themes.xml │ │ └── xml │ │ ├── display_color_prefs.xml │ │ ├── screen_preferences.xml │ │ ├── time_display_widget_info.xml │ │ └── widget_bkgnd_prefs.xml │ └── test │ └── java │ └── net │ └── diffengine │ └── romandigitalclock │ └── ExampleUnitTest.java ├── build.gradle.kts ├── fastlane └── metadata │ └── android │ └── en-US │ ├── changelogs │ ├── 1.txt │ ├── 2.txt │ ├── 3.txt │ ├── 4.txt │ ├── 5.txt │ ├── 6.txt │ ├── 7.txt │ ├── 8.txt │ └── 9.txt │ ├── full_description.txt │ ├── images │ ├── featureGraphic.png │ ├── icon.png │ ├── phoneScreenshots │ │ ├── p1.png │ │ ├── p10.jpg │ │ ├── p11.jpg │ │ ├── p2.png │ │ ├── p3.png │ │ ├── p4.png │ │ ├── p5.png │ │ ├── p6.png │ │ ├── p7.png │ │ ├── p8.png │ │ └── p9.png │ └── tenInchScreenshots │ │ ├── pA.png │ │ ├── pB.png │ │ ├── pC.png │ │ └── pD.png │ ├── short_description.txt │ └── title.txt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # RomanDigital funding platforms 2 | 3 | liberapay: nichedev 4 | custom: ['https://www.paypal.com/donate/?business=7J9528994S6QU&no_recurring=0¤cy_code=USD'] -------------------------------------------------------------------------------- /.github/images/Screenshot_20240809_113408_RomanDigital.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfyockey/RomanDigital/98594a64b34e3391e30f2eda14ce3056590f98ad/.github/images/Screenshot_20240809_113408_RomanDigital.png -------------------------------------------------------------------------------- /.github/images/Screenshot_20240809_161416_RomanDigital.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfyockey/RomanDigital/98594a64b34e3391e30f2eda14ce3056590f98ad/.github/images/Screenshot_20240809_161416_RomanDigital.png -------------------------------------------------------------------------------- /.github/images/Screenshot_20240809_205721_RomanDigital.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfyockey/RomanDigital/98594a64b34e3391e30f2eda14ce3056590f98ad/.github/images/Screenshot_20240809_205721_RomanDigital.png -------------------------------------------------------------------------------- /.github/images/Screenshot_20240910_174429_One_UI_Home_scaled.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfyockey/RomanDigital/98594a64b34e3391e30f2eda14ce3056590f98ad/.github/images/Screenshot_20240910_174429_One_UI_Home_scaled.jpg -------------------------------------------------------------------------------- /.github/images/Screenshot_20241008_143851_One_UI_Home_scaled.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfyockey/RomanDigital/98594a64b34e3391e30f2eda14ce3056590f98ad/.github/images/Screenshot_20241008_143851_One_UI_Home_scaled.jpg -------------------------------------------------------------------------------- /.github/images/Screenshot_20250219_115336_One_UI_Home_scaled.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfyockey/RomanDigital/98594a64b34e3391e30f2eda14ce3056590f98ad/.github/images/Screenshot_20250219_115336_One_UI_Home_scaled.jpg -------------------------------------------------------------------------------- /.github/images/Screenshot_20250219_120411_One_UI_Home_scaled.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfyockey/RomanDigital/98594a64b34e3391e30f2eda14ce3056590f98ad/.github/images/Screenshot_20250219_120411_One_UI_Home_scaled.jpg -------------------------------------------------------------------------------- /.github/images/Torn_Screenshot_20240913_171548_One_UI_Home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfyockey/RomanDigital/98594a64b34e3391e30f2eda14ce3056590f98ad/.github/images/Torn_Screenshot_20240913_171548_One_UI_Home.png -------------------------------------------------------------------------------- /.github/images/clearpixel.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfyockey/RomanDigital/98594a64b34e3391e30f2eda14ce3056590f98ad/.github/images/clearpixel.gif -------------------------------------------------------------------------------- /.github/images/get-the-latest-apk-on-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfyockey/RomanDigital/98594a64b34e3391e30f2eda14ce3056590f98ad/.github/images/get-the-latest-apk-on-github.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle files 2 | .gradle/ 3 | build/ 4 | 5 | # Local configuration file (sdk path, etc) 6 | local.properties 7 | 8 | # Log/OS Files 9 | *.log 10 | 11 | # Android Studio generated files and folders 12 | captures/ 13 | .externalNativeBuild/ 14 | .cxx/ 15 | *.apk 16 | output.json 17 | 18 | # IntelliJ 19 | *.iml 20 | .idea/ 21 | misc.xml 22 | deploymentTargetDropDown.xml 23 | render.experimental.xml 24 | 25 | # Keystore files 26 | *.jks 27 | *.keystore 28 | 29 | # Google Services (e.g. APIs or Firebase) 30 | google-services.json 31 | 32 | # Android Profiling 33 | *.hprof 34 | 35 | # OS X generated file 36 | .DS_Store 37 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Significant changes to the RomanDigital project will be documented here. 4 | 5 | The format of this changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). 6 | 7 | This project aims to adhere to [Semantic Versioning](https://server.org). 8 | 9 | Regarding project commits: As of 2024-08-23, this project aims to adhere to the [Conventional Commits](https://www.conventionalcommits.org) standard. While the standard makes recommendations, it does not limit commit type or scope; consequently, neither type nor scope is limited to those recommendations in the project commits. 10 | 11 | ## [2.0.2] - 2025-03-21 12 | 13 | ### Fixed 14 | 15 | * Failure of widget preferences being applied after a device's launcher has reset. Fixed by allowing update of preference-related features with every receipt of a MINUTE_TICK intent, negating a change in [2.0.0]. 16 | * Accuracy of preview in the widget picker on device's running Android 12 or later. 17 | 18 | ## [2.0.1] - 2025-03-07 19 | 20 | ### Fixed 21 | 22 | * Failure of widgets to restart at device boot. 23 | 24 | ## [2.0.0] - 2025-02-20 25 | 26 | ### Added 27 | 28 | * Ability to configure settings independently for app and for each of one or more widgets. Settings are distinguished by a postfix widget id added to preference keys corresponding to a given widget, and by app settings having no postfix. __[This addition may require the User to reset settings for any widgets currently in use.]__ 29 | * Ability to select a different time zone for each widget. 30 | * Selectable widget layouts enabling addition of a time zone label above or below the time display. 31 | * A custom preference that has no function except to provide a separator line in the arrangement of preferences on a settings activity. 32 | * The id of the widget currently being configured to the widget settings activity title when the project is built for debugging. 33 | * Commit type 'cleanup' for chores limited to removal of unused files, unused code, unneeded comments (or portions thereof), and/or superfluous whitespace. 34 | 35 | ### Changed 36 | 37 | * XML implementation of time and opacity preferences to code implementation to enable programmatic change of preference keys so the same hierarchy could be used to independently set different preference values for app and widget. 38 | * Tick intent (action MINUTE_TICK) so as to eliminate its unnecessary duplication. 39 | * Frequency of updating widget features by determining when to update such features based on the action of a received intent, especially preventing these features from being updated with every receipt of a MINUTE_TICK intent. 40 | 41 | ### Deprecated 42 | 43 | * The back arrow on the about activity, to be replaced with an "Ok" or similarly labeled button in the style of the buttons now provided on settings activities. 44 | 45 | ### Removed 46 | 47 | * The back arrow from the title bar of each settings activity. 48 | 49 | ### Fixed 50 | 51 | * Issues with widget time display text size being incorrect for the width of the widget (3 or >4 tiles wide) by adding setting of text size to be updated along with other widget features. 52 | 53 | ## [1.3.0] - 2024-11-18 54 | 55 | ### Added 56 | 57 | * Variable widget background opacity, with opacity being selectable from 0% to 100% by tens. 58 | * A slider — technically, a SeekBarPreference — to the widget settings activity so as to enable the user to select the desired opacity. 59 | * Automatic setting of widget text color in light mode to white (#FAFAFA) for opacity < 50% and to black (#040404) for opacity >= 50% to improve contrast for very white backgrounds. Text color is set to white (#FAFAFA) in dark mode regardless of background opacity. 60 | * Intent SETTINGS_KICK and modifications to the widget so SETTINGS_KICK is treated identically to MINUTE_TICK with respect to time updating and additionally used to enable update of background opacity when changed by the user or update is initiated by the widget itself. 61 | 62 | ### Changed 63 | 64 | * Widget corners for all Android versions to the same degree of curvature (8dp radius). 65 | * The broadcast of a kickstart intent in the widget config activity onPause method and calls to update the widget in the widget itself to include SETTINGS_KICK instead of MINUTE_TICK. 66 | 67 | ## [1.2.0] - 2024-10-29 68 | 69 | ### Added 70 | 71 | * `Cancel` and `Save` buttons to both widget and app settings activities (addresses Issue #12). 72 | 73 | ## [1.1.2] - 2024-10-19 74 | 75 | ### Fixed 76 | 77 | * App clock text being too large (Issue #14) when user has selected a font in their Android settings that effects app fonts and that does not provide a monospace font. (Note: While this fix enables use of variable-width fonts, the "Align to Divider" option functionality does not work correctly for such fonts.) 78 | 79 | ## [1.1.1] - 2024-10-15 80 | 81 | ### Changed 82 | 83 | * Location of versionName definition from strings.xml to app/build.gradle.kts; this facilitates building with systems (e.g. F-Droid) for which definition from a string reference is problematic. 84 | * AboutActivity to fetch versionName for display from app/build.gradle.kts rather than from a string reference. 85 | * Fastlane full_description to improve readability of reference to the README.md file. 86 | 87 | ## [1.1.0] - 2024-10-09 88 | 89 | ### Added 90 | 91 | * App metadata in a fastlane file structure for use in generating an app description page on [F-Droid](https://f-droid.org) 92 | * distributionSha256Sum value in gradle-wrapper.properties matching the SHA256SUM of file gradle-8.2-bin.zip referred to by the distributionUrl (see https://gradle.org/release-checksums/) to improve app security 93 | * FUNDING.yml containing funding platform information for accepting donations 94 | * Commit types 'meta' for metadata-related commits and 'improve' as short for 'improvement' (type 'improvement' is recommended in Conventional Commits beta versions 2-4). 95 | * This CHANGELOG file 96 | 97 | ### Changed 98 | 99 | * The default size of the app widget to its smallest size to facilitate installation of the widget on a crowded Home screen 100 | * The preview image used in widget pickers to one with a size matching the new default size 101 | * Display of the version on the About activity so it's set in the activity's onCreate method; this facilitates setting the versionName in app/build.gradle.kts, by way of a string reference, to just be a dotted number sequence 102 | * The version of gradle-wrapper.jar to match the version of gradle-8.2-bin.zip (i.e. 8.2) to improve app security 103 | * The README file 104 | 105 | ### Refactored 106 | 107 | * Deleted unneeded and/or unused matter from AndroidManifest.xml 108 | 109 | ## [1.0.0] - 2024-09-13 110 | 111 | ### Added 112 | 113 | * Everything. This is the first release. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Banner image : RomanDigital clock displayed on a phone resting horizontally on a black Lego stand to the left of a Lego Botanicals bonsai tree](/fastlane/metadata/android/en-US/images/featureGraphic.png) 2 | 3 | # RomanDigital 4 | 5 | Android Digital Clock App and Widget with Roman Numeral Display 6 | 7 | [Get it on F-Droid](https://f-droid.org/packages/net.diffengine.romandigitalclock/)
10 | ![F-Droid Version](https://img.shields.io/f-droid/v/net.diffengine.romandigitalclock?label=Latest%20Version)
11 |
12 |
13 | [Get the latest apk on GitHub](https://github.com/dfyockey/RomanDigital/releases/latest)
16 | ![GitHub Release](https://img.shields.io/github/v/release/dfyockey/RomanDigital?label=Latest%20Version)
17 |
18 | 19 | ## Description 20 | 21 | RomanDigital is a digital clock app that displays the current time in 22 | Roman numerals. It can be used to repurpose an old Android device that's 23 | sitting around collecting dust, or to provide a clock of unique style on 24 | a current phone or tablet. 25 | 26 | ![Landscape screenshot of phone showing RomanDigital app displaying time as VIII:LVII](/.github/images/Screenshot_20240809_205721_RomanDigital.png) 27 | 28 | Further, RomanDigital includes a widget that can be added to a device's Home screen. 29 | 30 | ![Portion of a portrait screenshot of a phone Home page showing RomanDigital widget displaying time as XVII:XV, the screenshot portion having a torn-paper-effect bottom edge](/.github/images/Torn_Screenshot_20240913_171548_One_UI_Home.png) 31 | 32 | ## Features 33 | 34 | RomanDigital includes several common clock app features, including: 35 | 36 | * Choice of 12 or 24 hour display 37 | * Centered display 38 | * Option to keep display on when app is in foreground 39 | * Display in either portrait or landscape 40 | * Tap on screen displays app and system controls: 41 | 42 | ![Landscape screenshot of phone showing system bars and RomanDigital app displaying time as IV:XIV with toolbar containing gear and info icons](/.github/images/Screenshot_20240809_161416_RomanDigital.png) 43 | 44 | RomanDigital further includes: 45 | 46 | * Choice between centered display and display aligned with a fixed divider 47 | * AM/PM indicator integrated into the time display, such that the divider is displayed as "·" for AM and ":" for PM: 48 | 49 | ![Landscape screenshot of phone showing RomanDigital app displaying time as XI·XXXIV](/.github/images/Screenshot_20240809_113408_RomanDigital.png) 50 | 51 | * Option to only keep display on when device is charging 52 | * Adaptive display providing the largest possible monospace text for the device screen width (excepting a narrow margin) 53 | * A widget for providing a Roman digital clock display on a device's Home screen: 54 | 55 | ![Portrait screenshot of phone Home screen showing RomanDigital widget](/.github/images/Screenshot_20240910_174429_One_UI_Home_scaled.jpg) 56 | 57 | * Independent setting of widget background transparency, time zone, and other settings: 58 | 59 | ![Portrait screenshot of a second phone Home screen showing four RomanDigital widgets with center-aligned Continental U.S. time zone times, time zone labels below times, and different transparency backgrounds](/.github/images/Screenshot_20250219_115336_One_UI_Home_scaled.jpg) ![Portrait screenshot of a third phone Home screen showing six RomanDigital widgets with fixed-divider-aligned international location time zone times and time zone labels above times](/.github/images/Screenshot_20250219_120411_One_UI_Home_scaled.jpg) 60 | 61 | * And... RomanDigital is Apache-2.0-licensed open source :slightly_smiling_face: 62 | 63 | ## Requirements 64 | 65 | RomanDigital requires Android 5.0 or greater and is designed to run on a phone or tablet. 66 | 67 | ## Widget Settings 68 | 69 | When the widget is added to the Home screen, an activity is displayed to allow selection of 70 | settings. This activity can be accessed later in one of two ways depending on the Android version. 71 | On Android 11 and earlier, a tap on the widget brings up the activity. On Android 12 and later, the 72 | activity is accessed by a long press on the widget and a tap on the settings or reconfigure button 73 | in the normal manner for the particular Android version. 74 | 75 | ## Permissions 76 | 77 | The USE_EXACT_ALARM permission is set by this app. This permission is 78 | necessary on Android 13 and greater to enable the widget to provide an 79 | accurate time display without inconveniencing the user by asking for the permission. 80 | It cannot be disabled without modifying the source code. 81 | 82 | The SCHEDULE_EXACT_ALARM permission is also set by this app and is 83 | necessary on Android 12 for the same reason as the USE_EXACT_ALARM 84 | permission discussed above. This permission can be disabled in the 85 | _Alarms & Reminders_ section of the system settings. Disabling this 86 | permission will cause inaccuracy in the widget's time display. 87 | 88 | Android versions 11 and lower allow setting of exact alarms by default. 89 | 90 | In addition, net.diffengine.romandigitalclock.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION 91 | is set by this app. This was set during the app build process by use of a particular Android 92 | library required to enable the app to function as expected on versions of Android 12 and earlier 93 | (i.e. prior to API 33). It cannot be disabled without modifying the source code. As noted in 94 | an fdroidserver commit, "it's basically just an internal hack, rather than a real permission." For 95 | a more technical explanation of this "permission", see the full commit message at 96 | https://gitlab.com/fdroid/fdroidserver/-/merge_requests/1336/diffs?commit_id=71697f9c88ec73980f63be5955f36cdc3ba7a02c 97 | 98 | ## Known Issues 99 | 100 | After building and updating the app in Android Studio, a widget that had previously been added to the home screen may stop updating. It may be 'kickstarted' by simply opening and then closing the widget's settings screen. 101 | 102 | The 'Align to Divider' option does not correctly align the display when a variable width font is used. 103 | 104 | The RomanDigital app will run on a 5th Generation Amazon Kindle Fire, which is based on Android 5.1, but the widget will not. RomanDigital has not been tested on other Fire versions. 105 | 106 | ## FAQ (Foremost Anticipated Questions) 107 | 108 | > Q: "I just updated RomanDigital / updated Android / restarted my device, and now the widget doesn't work! How do I get it running again?" 109 | > 110 | > A: As of version 2.0.1, this should only happen when running RomanDigital on a device through Android Studio. The workaround to get the widget going again is to "kickstart" it by simply opening and then canceling a settings screen from any widget or from the app. 111 | 112 | > Q: "Why does the position of the divider change when 'Align to Divider' is selected? Isn't it supposed to stay in one place?" 113 | > 114 | > A: Yes, it's supposed to stay in one place, but the positioning was designed with the expectation of using a monospace font. Shortsighted of me, I know, but implementation for variable width fonts would be _really hard_. If you've changed a device setting effecting the font used, e.g. your system-wide font, to something that doesn't provide for monospace, then the calculated display position based on expected equal-width characters, and thus the divider position, will unfortunately be off. 115 | 116 | > Q: "There's no alarm feature, so why the need to set exact alarms? What's this got to do with an accurate time display?" 117 | > 118 | > A: Android doesn't enable widgets to receive the ACTION_TIME_TICK intent broadcast through the system each minute. Setting an alarm for the exact time of each next minute at the end of a minute is the only straightforward way (that I know of) for the app's widget to know when to update the time display. 119 | 120 | > Q: "Will there ever be an alarm feature?" 121 | > 122 | > A: Maybe, but it's quite low on the priority list at this point. 123 | 124 | > Q: "Why can't I change the font/text color/background color/widget corner curvature/etc?" 125 | > 126 | > A: Background _transparency_ can now be changed, with background fully white/black for 100% opacity in light/dark mode. As for other stylistic changes, I haven't gotten to them yet. (_Spoiler Alert!_ Variable app text color is planned for version 2.1.0.) 127 | 128 | > Q: "Why no seconds?" 129 | > 130 | > A: They would crowd the display and look inelegant. Besides, most people (myself included) would likely have trouble reading a lengthy Roman numeral within a second. 131 | 132 | > Q: "Why no date?" 133 | > 134 | > A: Because it's just a simple clock. At least for now. 135 | 136 | > Q: "I want to change widget time zone labels to particular place names. Is there any way to do this?" 137 | > 138 | > A: Not right now, but it should be added pretty soon since I want that option myself and it should be easy to implement. 139 | 140 | > Q: "Can I put the widget on my phone's lock screen?" 141 | > 142 | > A: Maybe. I was able to on my Samsung Galaxy A14 5G by purchasing the awkwardly-named [Lockscreen Widgets and Drawer](https://play.google.com/store/apps/details?id=tk.zwander.lockscreenwidgets) app for the low, low price of $1.49. YMMV. Here's what my lock screen looks like: 143 | 144 | ![Portrait screenshot of phone lock screen showing RomanDigital widget](/.github/images/Screenshot_20241008_143851_One_UI_Home_scaled.jpg) 145 | 146 | > Note: 147 | > 148 | > * I get nothing if you click on the "Lock Screen Widgets and Drawer" link and/or buy the app, and my purchase and use of it are not meant as an endorsement. There may be other such apps that would work as well or better. 149 | 150 | > Q: "Why no version specifically for a watch?" 151 | > 152 | > A: Because I don't have one to test on. Yet. I'd rather not rely *entirely* on virtual devices for testing. 153 | 154 | > Q: "Will it work on a watch or a TV?" 155 | > 156 | > A: It might — haven't tried — but if it did, it would likely result in a time display that's "unoptimized for the given device's screen size." In other words, "too large" or "too small". 157 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | } 4 | 5 | android { 6 | namespace = "net.diffengine.romandigitalclock" 7 | compileSdk = 34 8 | 9 | defaultConfig { 10 | applicationId = "net.diffengine.romandigitalclock" 11 | minSdk = 21 12 | targetSdk = 34 13 | versionCode = 9 14 | versionName = "2.0.2" 15 | 16 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | isMinifyEnabled = false 22 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 23 | } 24 | } 25 | compileOptions { 26 | sourceCompatibility = JavaVersion.VERSION_1_8 27 | targetCompatibility = JavaVersion.VERSION_1_8 28 | } 29 | buildFeatures { 30 | viewBinding = true 31 | buildConfig = true 32 | } 33 | } 34 | 35 | dependencies { 36 | val acraVersion = "5.11.3" 37 | 38 | implementation("androidx.appcompat:appcompat:1.6.1") 39 | implementation("com.google.android.material:material:1.11.0") 40 | implementation("androidx.constraintlayout:constraintlayout:2.1.4") 41 | implementation("androidx.preference:preference:1.2.1") 42 | implementation("ch.acra:acra-mail:$acraVersion") 43 | implementation("ch.acra:acra-dialog:$acraVersion") 44 | testImplementation("junit:junit:4.13.2") 45 | androidTestImplementation("androidx.test.ext:junit:1.1.5") 46 | androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") 47 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/net/diffengine/romandigitalclock/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package net.diffengine.romandigitalclock; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("net.diffengine.romandigitalclock", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 39 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 53 | 54 | 55 | 58 | 59 | 60 | 61 | 62 | 63 | 70 | 71 | 72 | 73 | 74 | 80 | 85 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /app/src/main/java/net/diffengine/romandigitalclock/AboutActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * AboutActivity.java 3 | * - This file is part of the Android app RomanDigital 4 | * 5 | * Copyright 2024 David Yockey 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | */ 20 | 21 | package net.diffengine.romandigitalclock; 22 | 23 | import androidx.appcompat.app.ActionBar; 24 | import androidx.appcompat.app.AppCompatActivity; 25 | 26 | import android.os.Bundle; 27 | import android.widget.TextView; 28 | 29 | public class AboutActivity extends AppCompatActivity { 30 | 31 | @Override 32 | protected void onCreate(Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | setContentView(R.layout.about_activity); 35 | 36 | TextView tv = findViewById(R.id.tvVersion); 37 | 38 | // BuildConfig full package name is to prevent any possible future confusion with org.acra.BuildConfig 39 | String appversion = net.diffengine.romandigitalclock.BuildConfig.VERSION_NAME; 40 | 41 | tv.setText(getString(R.string.app_version_label, appversion)); 42 | 43 | ActionBar actionBar = getSupportActionBar(); 44 | if (actionBar != null) { 45 | actionBar.setDisplayHomeAsUpEnabled(true); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/net/diffengine/romandigitalclock/AppClass.java: -------------------------------------------------------------------------------- 1 | /* 2 | * AppClass.java 3 | * - This file is part of the Android app RomanDigital 4 | * 5 | * Copyright 2024 David Yockey 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | */ 20 | 21 | package net.diffengine.romandigitalclock; 22 | 23 | import android.app.Application; 24 | import android.content.Context; 25 | 26 | import org.acra.ACRA; 27 | import org.acra.config.CoreConfigurationBuilder; 28 | import org.acra.config.DialogConfigurationBuilder; 29 | import org.acra.config.MailSenderConfigurationBuilder; 30 | import org.acra.data.StringFormat; 31 | import org.acra.mail.BuildConfig; 32 | 33 | public class AppClass extends Application { 34 | @Override 35 | protected void attachBaseContext(Context base) { 36 | super.attachBaseContext(base); 37 | 38 | ACRA.init(this, new CoreConfigurationBuilder() 39 | //core configuration: 40 | .withBuildConfigClass(BuildConfig.class) 41 | .withReportFormat(StringFormat.KEY_VALUE_LIST) 42 | .withPluginConfigurations( 43 | new MailSenderConfigurationBuilder() 44 | .withMailTo("appissues@diffengine.net") 45 | .withReportAsFile(true) 46 | .withReportFileName("RomanDigital_Issue.txt") 47 | .withSubject(this.getPackageName() + " bug report") 48 | .build(), 49 | new DialogConfigurationBuilder() 50 | .withText(getString(R.string.dialog_text)) 51 | .withTitle(getString(R.string.app_name) + " Crashed") 52 | .build() 53 | ) 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/net/diffengine/romandigitalclock/BootCompletedBroadcastReceiver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * BootCompletedBroadcastReceiver.java 3 | * - This file is part of the Android app RomanDigital 4 | * 5 | * Copyright 2025 David Yockey 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | */ 20 | 21 | package net.diffengine.romandigitalclock; 22 | 23 | import static android.content.Intent.ACTION_BOOT_COMPLETED; 24 | 25 | import android.content.BroadcastReceiver; 26 | import android.content.Context; 27 | import android.content.Intent; 28 | 29 | public class BootCompletedBroadcastReceiver extends BroadcastReceiver { 30 | 31 | @Override 32 | public void onReceive(Context context, Intent intent) { 33 | String action = intent.getAction(); 34 | 35 | if (action != null && action.equals(ACTION_BOOT_COMPLETED)) { 36 | Intent kickstart = new Intent(context, TimeDisplayWidget.class); 37 | kickstart.setAction(TimeDisplayWidget.MINUTE_TICK); 38 | kickstart.setPackage(context.getPackageName()); 39 | context.sendBroadcast(kickstart); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/net/diffengine/romandigitalclock/ColorSeekBarView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ColorSeekBarView.java 3 | * - This file is part of the Android app RomanDigital 4 | * 5 | * Copyright 2025 David Yockey 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | */ 20 | 21 | package net.diffengine.romandigitalclock; 22 | 23 | import android.content.Context; 24 | import android.content.res.ColorStateList; 25 | import android.content.res.TypedArray; 26 | import android.graphics.PorterDuff; 27 | import android.os.Build; 28 | import android.util.AttributeSet; 29 | import android.widget.LinearLayout; 30 | import android.widget.SeekBar; 31 | import android.widget.TextView; 32 | 33 | import androidx.annotation.Nullable; 34 | import androidx.core.content.res.ResourcesCompat; 35 | 36 | public class ColorSeekBarView extends LinearLayout { 37 | 38 | CharSequence mtext; 39 | ColorStateList mcolor; 40 | ColorStateList mbkgnd; 41 | 42 | TextView barLabel; 43 | SeekBar barColor; 44 | 45 | public ColorSeekBarView(Context context) { 46 | super(context); 47 | init(context, null); 48 | } 49 | 50 | public ColorSeekBarView(Context context, @Nullable AttributeSet attrs) { 51 | super(context, attrs); 52 | init(context, attrs); 53 | } 54 | 55 | public ColorSeekBarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 56 | super(context, attrs, defStyleAttr); 57 | init(context, attrs); 58 | } 59 | 60 | @SuppressWarnings({"UnusedDeclaration"}) 61 | public ColorSeekBarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { 62 | super(context, attrs, defStyleAttr, defStyleRes); 63 | init(context, attrs); 64 | 65 | } 66 | 67 | private void init(Context context, @Nullable AttributeSet attrs) { 68 | inflate(context, R.layout.color_seekbar_layout, this); 69 | 70 | if (attrs != null) { 71 | TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ColorSeekBarView, 0, 0); 72 | 73 | try { 74 | mtext = a.getText(R.styleable.ColorSeekBarView_barLabel); 75 | mcolor = a.getColorStateList(R.styleable.ColorSeekBarView_barColor); 76 | mbkgnd = a.getColorStateList(R.styleable.ColorSeekBarView_barBackgroundColor); 77 | } finally { 78 | a.recycle(); 79 | } 80 | } 81 | } 82 | 83 | @Override 84 | protected void onFinishInflate() { 85 | super.onFinishInflate(); 86 | setupSeekBar(); 87 | } 88 | 89 | private void setupSeekBar() { 90 | barLabel = findViewById(R.id.colorLabel); 91 | barLabel.setText(mtext); 92 | 93 | barColor = findViewById(R.id.colorBar); 94 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 95 | barColor.setMin(0); 96 | } 97 | barColor.setMax(255); 98 | 99 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 100 | barColor.setProgressDrawable(ResourcesCompat.getDrawable(getContext().getResources(), R.drawable.seekbar_track_material, null)); 101 | } 102 | 103 | barColor.setProgressBackgroundTintMode(PorterDuff.Mode.SRC_IN); 104 | barColor.setProgressBackgroundTintList(mbkgnd); 105 | barColor.setProgressTintMode(PorterDuff.Mode.SRC_IN); 106 | barColor.setProgressTintList(mcolor); 107 | barColor.setThumbTintMode(PorterDuff.Mode.SRC_IN); 108 | barColor.setThumbTintList(mcolor); 109 | } 110 | 111 | public void setProgress(int progress) { 112 | barColor.setProgress(progress); 113 | } 114 | public int getProgress() { return barColor.getProgress(); } 115 | 116 | public void setOnColorSeekBarChangeListener(SeekBar.OnSeekBarChangeListener listener) { 117 | barColor.setOnSeekBarChangeListener(listener); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /app/src/main/java/net/diffengine/romandigitalclock/SettingsActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SettingsActivity.java 3 | * - This file is part of the Android app RomanDigital 4 | * 5 | * Copyright 2024-2025 David Yockey 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | */ 20 | 21 | package net.diffengine.romandigitalclock; 22 | 23 | import android.app.Dialog; 24 | import android.appwidget.AppWidgetManager; 25 | import android.content.BroadcastReceiver; 26 | import android.content.Context; 27 | import android.content.Intent; 28 | import android.content.IntentFilter; 29 | import android.os.Bundle; 30 | 31 | import androidx.annotation.NonNull; 32 | import androidx.annotation.Nullable; 33 | import androidx.appcompat.app.AppCompatActivity; 34 | import androidx.fragment.app.FragmentManager; 35 | import androidx.preference.ListPreference; 36 | import androidx.preference.Preference; 37 | import androidx.preference.PreferenceCategory; 38 | import androidx.preference.PreferenceFragmentCompat; 39 | import androidx.preference.PreferenceManager; 40 | import androidx.preference.PreferenceScreen; 41 | import androidx.preference.SwitchPreferenceCompat; 42 | 43 | import java.util.TimeZone; 44 | 45 | public class SettingsActivity extends AppCompatActivity { 46 | 47 | DisplayColorFragment displayColorFragment; 48 | 49 | @Override 50 | protected void onCreate(Bundle savedInstanceState) { 51 | super.onCreate(savedInstanceState); 52 | setContentView(R.layout.settings_activity); 53 | if (savedInstanceState == null) { 54 | displayColorFragment = new DisplayColorFragment(); 55 | FragmentManager supportFragmentManager = getSupportFragmentManager(); 56 | supportFragmentManager 57 | .beginTransaction() 58 | .setReorderingAllowed(true) 59 | .add(R.id.app_settings_frame, new SettingsFragment(true, AppWidgetManager.INVALID_APPWIDGET_ID)) 60 | .add(R.id.display_color_frame, displayColorFragment) 61 | .add(R.id.screen_settings_frame, new ScreenSettingsFragment()) 62 | .add(R.id.button_bar_2, new SettingsButtonBarFragment()) 63 | .commit(); 64 | } else { 65 | FragmentManager supportFragmentManager = getSupportFragmentManager(); 66 | displayColorFragment = (DisplayColorFragment) supportFragmentManager.findFragmentById(R.id.display_color_frame); 67 | } 68 | } 69 | 70 | public static class SettingsFragment extends PreferenceFragmentCompat { 71 | 72 | static String postfix; 73 | 74 | public SettingsFragment () { 75 | } 76 | 77 | public SettingsFragment (Boolean isApp, int appWidgetId) { 78 | final String appPostfix = ""; 79 | final String widgetPostfix = String.valueOf(appWidgetId); 80 | postfix = ( (isApp) ? appPostfix : widgetPostfix ); 81 | } 82 | 83 | private void setSeparatorEnableState(SwitchPreferenceCompat pFormat) { 84 | SwitchPreferenceCompat pSeparator = findPreference("switch_separator" + postfix); 85 | if (pFormat.isChecked() == MainActivity.right) { 86 | //noinspection DataFlowIssue 87 | pSeparator.setChecked(MainActivity.left); 88 | pSeparator.setEnabled(false); 89 | } else { 90 | //noinspection DataFlowIssue 91 | pSeparator.setEnabled(true); 92 | } 93 | } 94 | 95 | Context prefManagerContext; 96 | PreferenceCategory category; 97 | 98 | private void addABSwitchPreference (String key, String aText, String bText) { 99 | SwitchPreferenceCompat pref = new SwitchPreferenceCompat(prefManagerContext); 100 | pref.setLayoutResource(R.layout.a_b_switch_layout); 101 | pref.setKey(key + postfix); 102 | pref.setDefaultValue(false); 103 | pref.setTitle(aText); 104 | pref.setSummary(bText); 105 | category.addPreference(pref); 106 | } 107 | 108 | private void addSeparator (String key) { 109 | Preference pref = new Preference(prefManagerContext); 110 | pref.setLayoutResource(R.layout.separator_layout); 111 | pref.setKey(key); 112 | category.addPreference(pref); 113 | } 114 | 115 | private void addListPreference (String key, String title, String[] entries, String[] entryValues, String defaultValue) { 116 | ListPreference pref = new ListPreference(prefManagerContext); 117 | pref.setIconSpaceReserved(true); // Required for some devices that default this to false 118 | pref.setKey(key + postfix); 119 | pref.setTitle(title); 120 | 121 | // "%s" is documented in the doc for the deprecated android.preference.ListPreference at 122 | // https://developer.android.com/reference/android/preference/ListPreference.html#setSummary(java.lang.CharSequence), 123 | // but not in the doc for its replacement androidx.preference.ListPreference at 124 | // https://developer.android.com/reference/androidx/preference/ListPreference#setSummary(java.lang.CharSequence). 125 | // 126 | // Consequently, while it's EXTREMELY useful, it should be considered deprecated unless 127 | // and until it is documented in the androidx.preference.ListPreference documentation. 128 | pref.setSummary("%s"); 129 | 130 | pref.setEntries(entries); 131 | pref.setEntryValues(entryValues); 132 | pref.setValue(defaultValue); 133 | 134 | category.addPreference(pref); 135 | } 136 | 137 | @Override 138 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 139 | PreferenceManager manager = getPreferenceManager(); 140 | prefManagerContext = manager.getContext(); 141 | PreferenceScreen screen = manager.createPreferenceScreen(prefManagerContext); 142 | 143 | category = new PreferenceCategory(prefManagerContext); 144 | category.setTitle("Time"); 145 | category.setIconSpaceReserved(false); 146 | screen.addPreference(category); 147 | 148 | addABSwitchPreference("switch_format", "12 Hour", "24 Hour"); 149 | addABSwitchPreference("switch_alignment", "Align to Center", "Align to Divider"); 150 | addABSwitchPreference("switch_separator", ": for All", "· for AM\n: for PM"); 151 | 152 | if (!postfix.equals("")) { 153 | addSeparator("S1"); 154 | 155 | String[] timezoneIds = TimeZone.getAvailableIDs(); 156 | addListPreference("list_timezone", "Time Zone", timezoneIds, timezoneIds, TimeZone.getDefault().getID() ); 157 | 158 | String[] layoutEntries = {getString(R.string.tz_above_time), getString(R.string.time_only), getString(R.string.tz_below_time)}; 159 | String[] layoutValues = {"hi_label", "no_label", "lo_label"}; 160 | 161 | addListPreference("list_widget_layout", "Display Layout", layoutEntries, layoutValues, layoutValues[1] ); 162 | } 163 | 164 | setPreferenceScreen(screen); 165 | 166 | // At start of the activity, ensure that the separator switch is disabled and set to 167 | // left if the format switch is set to right (i.e. 24 hour format). 168 | // 169 | SwitchPreferenceCompat pFormat = findPreference("switch_format" + postfix); 170 | //noinspection DataFlowIssue 171 | setSeparatorEnableState(pFormat); 172 | } 173 | 174 | @Override 175 | public boolean onPreferenceTreeClick(@NonNull Preference preference) { 176 | if (preference.getKey().equals("switch_format" + postfix)) { 177 | // 178 | // Set separator switch enable and check states based on whether format switch state 179 | // is left or right (i.e. whether format is 12 or 24 hour). Implementation in code 180 | // of the enable/disable operation is needed because it is opposite to that provided 181 | // by the normal preference dependency attribute. 182 | // 183 | SwitchPreferenceCompat pFormat = (SwitchPreferenceCompat)preference; 184 | setSeparatorEnableState(pFormat); 185 | } 186 | return super.onPreferenceTreeClick(preference); 187 | } 188 | } 189 | 190 | public static class ScreenSettingsFragment extends PreferenceFragmentCompat { 191 | @Override 192 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 193 | setPreferencesFromResource(R.xml.screen_preferences, rootKey); 194 | } 195 | } 196 | 197 | public static boolean isHexColor(String hex) { 198 | return !( hex == null || !hex.matches("[0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f]") ); 199 | } 200 | 201 | public static class DisplayColorFragment extends PreferenceFragmentCompat { 202 | Context prefManagerContext; 203 | PreferenceCategory category; 204 | 205 | @Override 206 | public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { 207 | PreferenceManager manager = getPreferenceManager(); 208 | prefManagerContext = manager.getContext(); 209 | PreferenceScreen screen = manager.createPreferenceScreen(prefManagerContext); 210 | 211 | category = new PreferenceCategory(prefManagerContext); 212 | category.setIconSpaceReserved(false); 213 | category.setTitle("Style"); 214 | screen.addPreference(category); 215 | 216 | ColorDialogPreference colorPref = new ColorDialogPreference(prefManagerContext, getChildFragmentManager()); 217 | colorPref.setTitle("Time Color"); 218 | colorPref.setKey("hexcolor"); 219 | category.addPreference(colorPref); 220 | 221 | setPreferenceScreen(screen); 222 | } 223 | 224 | public void updateDialogTimeDisplayPreview() { 225 | ColorDialogPreference colorDialogPreference = category.findPreference("hexcolor"); //colorPref; //DisplayColorFragment.getColorDialogPreference(); 226 | ColorDialogPreference.ColorDialogFragment colorDialogFragment = colorDialogPreference.getColorDialogFragment(); 227 | 228 | if (colorDialogFragment != null) { 229 | Dialog dialog = colorDialogFragment.getDialog(); 230 | if (dialog != null && dialog.isShowing()) { 231 | colorDialogFragment.updatePreviewTime(); 232 | } 233 | } 234 | } 235 | } 236 | 237 | private class BroadcastReceiverEx extends BroadcastReceiver { 238 | @Override 239 | public void onReceive(Context context, Intent intent) { 240 | if (displayColorFragment != null) { 241 | displayColorFragment.updateDialogTimeDisplayPreview(); 242 | } 243 | } 244 | } 245 | 246 | // Receiver instance to be registered as exported for receiving system-broadcast ACTION_TIME_TICK intent 247 | private final BroadcastReceiverEx broadcastReceiver = new BroadcastReceiverEx(); 248 | 249 | @Override 250 | protected void onPause() { 251 | super.onPause(); 252 | 253 | // Kick the widgets so they'll update time immediately in case some broadcast intent has 254 | // been missed. This may be unnecessary; I tend to think of intents as similar to messages 255 | // used in controlling other systems' GUIs, which may not be the case. 256 | Intent kickstart = new Intent(this, TimeDisplayWidget.class); 257 | kickstart.setAction(TimeDisplayWidget.MINUTE_TICK); 258 | kickstart.setPackage(this.getPackageName()); 259 | this.sendBroadcast(kickstart); 260 | 261 | unregisterReceiver(broadcastReceiver); 262 | } 263 | 264 | @Override 265 | protected void onResume() { 266 | super.onResume(); 267 | 268 | registerReceiver(broadcastReceiver, new IntentFilter(Intent.ACTION_TIME_TICK)); 269 | } 270 | } -------------------------------------------------------------------------------- /app/src/main/java/net/diffengine/romandigitalclock/SettingsButtonBarFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SettingsButtonBarFragment.java 3 | * - This file is part of the Android app RomanDigital 4 | * 5 | * Copyright 2024-2025 David Yockey 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | */ 20 | 21 | package net.diffengine.romandigitalclock; 22 | 23 | import static android.app.Activity.RESULT_OK; 24 | 25 | import android.annotation.SuppressLint; 26 | import android.app.Activity; 27 | import android.appwidget.AppWidgetManager; 28 | import android.content.Intent; 29 | import android.content.SharedPreferences; 30 | import android.os.Bundle; 31 | 32 | import androidx.fragment.app.Fragment; 33 | import androidx.preference.PreferenceManager; 34 | 35 | import android.view.LayoutInflater; 36 | import android.view.View; 37 | import android.view.ViewGroup; 38 | import android.widget.Button; 39 | 40 | import java.util.Map; 41 | import java.util.Objects; 42 | 43 | public class SettingsButtonBarFragment extends Fragment implements View.OnClickListener { 44 | Activity parentActivity; 45 | Button btnCancel; 46 | Button btnSave; 47 | 48 | Map origprefs; // Storage for backup of original preference values 49 | SharedPreferences prefs; 50 | 51 | public SettingsButtonBarFragment() { 52 | // If the layout isn't provided to the superclass, it's necessary to use a form of 53 | // fragmentTransaction.add or .replace that takes the fragment class rather than an 54 | // instance thereof to instantiate the fragment in an activity's onCreate method. 55 | // Otherwise, onCreateView will never be called. 56 | super(R.layout.fragment_settings_button_bar); 57 | } 58 | 59 | @Override 60 | public void onCreate(Bundle savedInstanceState) { 61 | super.onCreate(savedInstanceState); 62 | 63 | parentActivity = requireActivity(); 64 | prefs = PreferenceManager.getDefaultSharedPreferences(requireContext()); 65 | origprefs = prefs.getAll(); 66 | } 67 | 68 | @SuppressLint("ApplySharedPref") 69 | public void onClick (View v) { 70 | 71 | // Get the appWidgetId if we're in a widget config activity 72 | // 73 | //noinspection ReassignedVariable 74 | int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; 75 | String cls = parentActivity.getComponentName().getClassName(); 76 | if (cls.equals("net.diffengine.romandigitalclock.TimeDisplayWidgetConfigActivity")) { 77 | TimeDisplayWidgetConfigActivity widgetConfigActivity = (TimeDisplayWidgetConfigActivity) parentActivity; 78 | appWidgetId = widgetConfigActivity.appWidgetId; 79 | } 80 | 81 | if (v == btnCancel) { 82 | // Set preferences back to original values 83 | SharedPreferences.Editor spEditor = prefs.edit(); 84 | 85 | for (String key : origprefs.keySet()) { 86 | Object value = origprefs.get(key); 87 | String prefType = Objects.requireNonNull(value).getClass().getSimpleName(); 88 | 89 | switch (prefType) { 90 | case "Boolean": 91 | spEditor.putBoolean(key, (boolean) value); 92 | break; 93 | case "Integer": 94 | spEditor.putInt(key, (int) value); 95 | break; 96 | case "String": 97 | spEditor.putString(key, (String) value); 98 | break; 99 | default: 100 | break; 101 | } 102 | } 103 | 104 | spEditor.commit(); 105 | 106 | } else if (v == btnSave) { 107 | if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) { 108 | // Provision of appwidget id in the extra data should prevent crash of some UIs 109 | // (e.g. TouchWiz on old Samsung devices) at activity destruction. 110 | // See https://stackoverflow.com/a/40709721 111 | Intent result = new Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); 112 | parentActivity.setResult(RESULT_OK, result); 113 | } 114 | } else { 115 | // In case of some shortsighted modification... :) 116 | return; 117 | } 118 | 119 | parentActivity.finish(); 120 | } 121 | 122 | @Override 123 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 124 | Bundle savedInstanceState) { 125 | View v = inflater.inflate(R.layout.fragment_settings_button_bar, container, false); 126 | 127 | btnCancel = v.findViewById(R.id.buttonCancel); 128 | btnCancel.setOnClickListener(this); 129 | 130 | btnSave = v.findViewById(R.id.buttonSave); 131 | btnSave.setOnClickListener(this); 132 | 133 | return v; 134 | } 135 | } -------------------------------------------------------------------------------- /app/src/main/java/net/diffengine/romandigitalclock/TimeDisplayWidgetConfigActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * TimeDisplayWidgetConfigActivity.java 3 | * - This file is part of the Android app RomanDigital 4 | * 5 | * Copyright 2024-2025 David Yockey 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | */ 20 | 21 | package net.diffengine.romandigitalclock; 22 | 23 | import androidx.annotation.NonNull; 24 | import androidx.annotation.Nullable; 25 | import androidx.appcompat.app.AppCompatActivity; 26 | import androidx.fragment.app.FragmentTransaction; 27 | import androidx.preference.Preference; 28 | import androidx.preference.PreferenceCategory; 29 | import androidx.preference.PreferenceFragmentCompat; 30 | import androidx.preference.PreferenceManager; 31 | import androidx.preference.PreferenceScreen; 32 | import androidx.preference.SeekBarPreference; 33 | 34 | import android.appwidget.AppWidgetManager; 35 | import android.content.Context; 36 | import android.content.Intent; 37 | import android.os.Bundle; 38 | 39 | import java.util.Objects; 40 | 41 | public class TimeDisplayWidgetConfigActivity extends AppCompatActivity { 42 | int appWidgetId; 43 | 44 | public TimeDisplayWidgetConfigActivity() { 45 | super(R.layout.activity_time_display_widget_config); 46 | } 47 | 48 | private void setResultCanceled() { 49 | // Enable cancellation of the configuration and, 50 | // if it's being added, app widget addition to home screen. 51 | // See https://developer.android.com/develop/ui/views/appwidgets/configuration#java 52 | // Provision of appwidget id in the extra data should also 53 | // prevent crash of TouchWiz on old Samsung devices at activity destruction. 54 | // See https://stackoverflow.com/a/40709721 55 | Intent intent = getIntent(); 56 | Bundle extras = intent.getExtras(); 57 | appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; 58 | if (extras != null) { 59 | appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); 60 | } 61 | Intent result = new Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); 62 | setResult(RESULT_CANCELED, result); 63 | } 64 | 65 | @Override 66 | protected void onCreate(Bundle savedInstanceState) { 67 | super.onCreate(savedInstanceState); 68 | setResultCanceled(); 69 | setContentView(R.layout.activity_time_display_widget_config); 70 | 71 | if(BuildConfig.DEBUG) { 72 | String activityTitle = (String) getTitle(); 73 | setTitle(activityTitle + " - " + appWidgetId); 74 | } 75 | 76 | if (savedInstanceState == null) { 77 | FragmentTransaction fragmentTransaction = getSupportFragmentManager() 78 | .beginTransaction() 79 | .setReorderingAllowed(true); 80 | 81 | fragmentTransaction.add(R.id.widget_settings, new SettingsActivity.SettingsFragment(false, appWidgetId)); 82 | fragmentTransaction.add(R.id.widget_bkgnd, new WidgetBkgndSettingsFragment(appWidgetId)); 83 | fragmentTransaction.add(R.id.button_bar, new SettingsButtonBarFragment()).commit(); 84 | } 85 | } 86 | 87 | public static class WidgetBkgndSettingsFragment extends PreferenceFragmentCompat { 88 | 89 | static String postfix; 90 | 91 | public WidgetBkgndSettingsFragment () { 92 | } 93 | 94 | public WidgetBkgndSettingsFragment (int appWidgetId) { 95 | postfix = String.valueOf(appWidgetId); 96 | } 97 | 98 | String buildOpacityLabel(int rawvalue) { 99 | int percentage = rawvalue * 10; 100 | return "Opacity: " + percentage + "%"; 101 | } 102 | 103 | Context prefManagerContext; 104 | PreferenceCategory category; 105 | 106 | @SuppressWarnings("SameParameterValue") // From https://stackoverflow.com/a/48734923/ 107 | private void addSeekBarPreference (String key) { 108 | SeekBarPreference pref = new SeekBarPreference(prefManagerContext); 109 | pref.setKey(key + postfix); 110 | pref.setMax(10); 111 | pref.setShowSeekBarValue(false); 112 | pref.setUpdatesContinuously(true); 113 | pref.setSummary("Opacity: %"); 114 | 115 | // Required for some devices that default this to false 116 | pref.setIconSpaceReserved(true); 117 | 118 | category.addPreference(pref); 119 | } 120 | 121 | @Override 122 | public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { 123 | PreferenceManager manager = getPreferenceManager(); 124 | prefManagerContext = manager.getContext(); 125 | PreferenceScreen screen = manager.createPreferenceScreen(prefManagerContext); 126 | 127 | category = new PreferenceCategory(prefManagerContext); 128 | category.setTitle("Background"); 129 | category.setIconSpaceReserved(false); 130 | screen.addPreference(category); 131 | 132 | addSeekBarPreference("seekbar_opacity"); 133 | 134 | setPreferenceScreen(screen); 135 | 136 | SeekBarPreference seekBarPreference = findPreference("seekbar_opacity" + postfix); 137 | Objects.requireNonNull(seekBarPreference).setSummary( buildOpacityLabel(seekBarPreference.getValue()) ); 138 | 139 | seekBarPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { 140 | @Override 141 | public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) { 142 | SeekBarPreference seekBarPref = (SeekBarPreference) preference; 143 | seekBarPref.setSummary( buildOpacityLabel((int)newValue) ); 144 | return true; 145 | } 146 | }); 147 | } 148 | } 149 | 150 | @Override 151 | protected void onResume() { 152 | super.onResume(); 153 | } 154 | 155 | @Override 156 | protected void onPause() { 157 | super.onPause(); 158 | 159 | // Since the widget's alarms may have been canceled on pause, 160 | // broadcast an intent to kickstart the widget when it resumes 161 | // along with immediately updating the widget 162 | Intent kickstart = new Intent(this, TimeDisplayWidget.class); 163 | kickstart.setAction(TimeDisplayWidget.SETTINGS_KICK); 164 | kickstart.setPackage(this.getPackageName()); 165 | this.sendBroadcast(kickstart); 166 | } 167 | 168 | @Override 169 | protected void onDestroy() { 170 | super.onDestroy(); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /app/src/main/java/net/diffengine/romandigitalclock/romantime.java: -------------------------------------------------------------------------------- 1 | /* 2 | * romantime.java 3 | * - This file is part of the Android app RomanDigital 4 | * 5 | * Copyright 2024-2025 David Yockey 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | */ 20 | 21 | package net.diffengine.romandigitalclock; 22 | 23 | import java.util.Calendar; 24 | import java.util.TimeZone; 25 | 26 | /** @noinspection SpellCheckingInspection*/ 27 | public class romantime { 28 | private static String itor(int i) { 29 | String[] R = { 30 | "","I","II","III","IV","V","VI","VII","VIII","IX", 31 | "","X","XX","XXX","XL","L" 32 | }; 33 | return R[(i/10)+10] + R[i%10]; 34 | } 35 | 36 | private static String getHours (Calendar cal, boolean ampm) { 37 | int h = (ampm) ? cal.get(Calendar.HOUR) : cal.get(Calendar.HOUR_OF_DAY); 38 | return (ampm) ? itor( (h>0)?h:12 ) : itor(h); 39 | } 40 | 41 | /** @noinspection ReassignedVariable*/ 42 | private static String getSeparator (Calendar cal, boolean ampm, boolean ampmSeparator) { 43 | String separator = ":"; 44 | if (ampm) { 45 | separator = ( (ampmSeparator && (cal.get(Calendar.AM_PM) == Calendar.AM) ) ? "·" : ":"); 46 | } 47 | return separator; 48 | } 49 | 50 | public static String now(boolean ampm, boolean ampmSeparator, boolean center, String tzId) { 51 | 52 | // ampm ampmSeparator 53 | // T T 12 hr / ampm separator 54 | // T F 12 hr / constant separator 55 | // F - 24 hr / constant separator 56 | 57 | /* GET TIME */ 58 | Calendar cal = Calendar.getInstance(); 59 | cal.setTimeZone(TimeZone.getTimeZone(tzId)); 60 | String rHours = getHours(cal, ampm); 61 | String rMinutes = itor(cal.get(Calendar.MINUTE)); 62 | String separator = getSeparator(cal, ampm, ampmSeparator); 63 | //noinspection ReassignedVariable 64 | String rtime = rHours + separator + rMinutes; 65 | 66 | if (!center) { 67 | /* ADD PADDING */ 68 | // Each `NBSP` in the following is a "U+00A0 NO-BREAK SPACE". 69 | // These are treated by a TextView as visible chars rather than whitespace. 70 | // 71 | // Note also that four padding spaces are needed for the ampm (i.e. 12-hour) format, 72 | // even though none will then be used for VIII o'clock, because the substring method to 73 | // retrieve the appropriate length of padding will throw an exception due to reading 74 | // past the end of the string if only three are used. As is, it returns "" for VIII. 75 | String lpad = ("    " + ((ampm) ? "" : " ")).substring(rHours.length()); 76 | String rpad = "       ".substring(rMinutes.length()); 77 | rtime = lpad + rtime + rpad; 78 | } 79 | 80 | return rtime; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/rd_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfyockey/RomanDigital/98594a64b34e3391e30f2eda14ce3056590f98ad/app/src/main/rd_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/res/color/ab_switch_thumb.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25 | 26 | 27 | 29 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/color/ab_switch_track.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25 | 26 | 27 | 29 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/app_widget_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25 | 26 | 30 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/app_widget_inner_view_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25 | 26 | 30 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/appwidget_bkgnd_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/appwidget_bkgnd_10.xml: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/appwidget_bkgnd_100.xml: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/appwidget_bkgnd_20.xml: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/appwidget_bkgnd_30.xml: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/appwidget_bkgnd_40.xml: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/appwidget_bkgnd_50.xml: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/appwidget_bkgnd_60.xml: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/appwidget_bkgnd_70.xml: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/appwidget_bkgnd_80.xml: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/appwidget_bkgnd_90.xml: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info_outline_24.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings_24dp.xml: -------------------------------------------------------------------------------- 1 | 16 | 21 | 24 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/romandigital_3x1_appwidget_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfyockey/RomanDigital/98594a64b34e3391e30f2eda14ce3056590f98ad/app/src/main/res/drawable/romandigital_3x1_appwidget_preview.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/seekbar_track_material.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 31 | 32 | 34 | 35 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 47 | 48 | 49 | 51 | 52 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/res/layout/a_b_switch_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25 | 26 | 30 | 31 | 45 | 46 | 58 | 59 | 71 | 72 | -------------------------------------------------------------------------------- /app/src/main/res/layout/about_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25 | 26 | 32 | 33 | 40 | 41 | 44 | 45 | 46 | 47 | 60 | 61 | 73 | 74 | 86 | 87 | 95 | 96 | 97 | 98 | 105 | 106 | 118 | 119 | 136 | 137 | 146 | 147 | 163 | 164 | 182 | 183 | 192 | 193 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25 | 26 | 35 | 36 | 47 | 48 | 72 | 73 | 92 | 93 | 106 | 107 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_time_display_widget_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25 | 26 | 32 | 33 | 43 | 44 | 47 | 48 | 55 | 56 | 63 | 64 | 65 | 66 | 67 | 68 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /app/src/main/res/layout/color_dialog_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25 | 26 | 32 | 33 | 51 | 52 | 64 | 65 | 83 | 84 | 92 | 93 | 96 | 97 | 110 | 111 | 123 | 124 | 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /app/src/main/res/layout/color_seekbar_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25 | 26 | 30 | 31 | 39 | 40 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_settings_button_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25 | 26 | 33 | 34 |