diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..64043843f --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,31 @@ +name: Build & Test + +on: + push: + branches: + - dev + pull_request: + branches: + - dev + +jobs: + build: + runs-on: macOS-latest + steps: + - uses: actions/checkout@v1 + - name: Install Java Development Kit 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Build APK & Run small tests + run: android/build.sh build + - name: Run medium tests + uses: ReactiveCircus/android-emulator-runner@v2.2.0 + with: + api-level: 29 + script: android/build.sh medium-tests + - name: Upload artifacts + uses: actions/upload-artifact@v1 + with: + name: Build + path: android/uhabits-android/build/outputs/ diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..d52e446ab --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,40 @@ +name: Build, Test & Publish + +on: + push: + branches: + - master + +jobs: + build: + runs-on: macOS-latest + steps: + - uses: actions/checkout@v1 + - name: Install GPG + uses: olafurpg/setup-gpg@v2 + - name: Decrypt secrets + env: + GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }} + run: .secret/decrypt.sh + - name: Install Java Development Kit 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Build APK & Run small tests + env: + RELEASE: 1 + run: android/build.sh build + - name: Run medium tests + uses: ReactiveCircus/android-emulator-runner@v2.2.0 + env: + RELEASE: 1 + with: + api-level: 29 + script: android/build.sh medium-tests + - name: Upload build to GitHub + uses: actions/upload-artifact@v1 + with: + name: Build + path: android/uhabits-android/build/outputs/ + - name: Upload APK to Google Play + run: cd android && ./gradlew publishReleaseApk diff --git a/.gitignore b/.gitignore index a8c758cef..ac138eede 100644 --- a/.gitignore +++ b/.gitignore @@ -4,13 +4,20 @@ *.perspectivev3 *.swp *~.nib +*.hprof .DS_Store +._.DS_Store .externalNativeBuild .gradle .idea +.secret build build/ captures local.properties node_modules *xcuserdata* +*.sketch +/design +/releases +/screenshots diff --git a/.secret/decrypt.sh b/.secret/decrypt.sh new file mode 100755 index 000000000..bde9192bb --- /dev/null +++ b/.secret/decrypt.sh @@ -0,0 +1,16 @@ +#!/bin/sh +cd "$(dirname "$0")" +if [ -z "$GPG_PASSWORD" ]; then + echo Env variable GPG_PASSWORD must be defined + exit 1 +fi +gpg \ + --quiet \ + --batch \ + --yes \ + --decrypt \ + --passphrase="$GPG_PASSWORD" \ + --output secret.tar.gz \ + secret +tar -xzf secret.tar.gz +rm secret.tar.gz diff --git a/.secret/secret b/.secret/secret new file mode 100644 index 000000000..e3f06089b Binary files /dev/null and b/.secret/secret differ diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c7278a60..e976b8d35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # Changelog +### 1.8.8 (June 21, 2020) + +* Make small changes to the habit scheduling algorithm, so that "1 time every x days" habits work more predictably. +* Fix crash when saving habit + +### 1.8.0 (Jan 1, 2020) + +* New bar chart showing number of repetitions performed in each week, month, quarter or year. +* Improved calculation of streaks for non-daily habits: performing habits on irregular weekdays will no longer break your streak. +* Many more colors to choose from (now 20 in total). +* Ability to customize how transparent the widgets are on your home screen. +* Ability to customize the first day of the week. +* Yes/No buttons on notifications, instead of just "Check". +* Automatic dark theme according to phone settings (Android 10). +* Smaller APK and backup files. +* Many other internal code changes improving performance and stability. + +### 1.7.11 (Aug 10, 2019) + +* Fix bug that produced corrupted CSV files in some countries + +### 1.7.10 (June 15, 2019) + +* Fix bug that prevented some devices from showing notifications. +* Update targetSdk to Android Pie (API level 28) + ### 1.7.8 (April 21, 2018) * Add support for adaptive icons (Oreo) diff --git a/README.md b/README.md index abc463653..72836df95 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ source.

Get it on Google Play - Git if on F-Droid + Get it on F-Droid

## Screenshots @@ -21,34 +21,32 @@ source. ## Features -* **Simple, beautiful and modern interface.** Loop has a minimalistic interface - that is easy to use and follows the material design guidelines. +* Beautiful, minimalistic and lightweight interface. +Loop has an elegant and minimalistic interface that is very easy to use, even for first-time users. Highly optimized for speed, the app works well even on older phones. -* **Habit score.** In addition to showing your current streak, Loop has an - advanced algorithm for calculating the strength of your habits. Every - repetition makes your habit stronger, and every missed day makes it weaker. A - few missed days after a long streak, however, will not completely destroy - your entire progress. +* Habit score. +Loop has an advanced formula for calculating the strength of your habits. Every repetition makes your habit stronger and every missed day makes it weaker. A few missed days after a long streak, however, will not completely destroy your progress, unlike many other don't-break-the-chain apps. -* **Detailed graphs and statistics.** Clearly see how your habits improved over - time with beautiful and detailed graphs. Scroll back to see the complete - history of your habits. +* Flexible schedules. +In addition to daily habits, Loop supports habits with more complex schedules, such as 3 times per week or every other day. -* **Flexible schedules.** Supports both daily habits and habits with more - complex schedules, such as 3 times every week; one time every other week; or - every other day. +* Reminders. +Schedule notifications to remind you of your habits. Each habit can have its own reminder, at a chosen time of the day. Easily check or dismiss your habit directly from the notification. -* **Reminders.** Create an individual reminder for each habit, at a chosen hour - of the day. Easily check, dismiss or snooze your habit directly from the - notification, without opening the app. +* Widgets. +Be reminded of your habits whenever you unlock your phone. Colorful widgets allow you to track your habits directly from your home screen, without even opening the app. -* **Optimized for smartwatches.** Reminders can be checked, snoozed or - dismissed directly from your Android Wear watch. +* Take control of your data. +If you want to further analyze your data, or move it to another service, Loop allows you to export it to spreadsheets (CSV) or to a database file (SQLite). For power users, checkmarks can be added through other apps, such as Tasker. -* **Completely ad-free and open source.** There are absolutely no - advertisements, annoying notifications or intrusive permissions in this app, - and there will never be. The complete source code is available under the - GPLv3. +* No limitations. +Track as many habits as you wish. Loop imposes no artificial limits on how many habits you can have. All features are available to all users. There are no in-app purchases. + +* Completely ad-free and open source. +There are no advertisements, annoying notifications or intrusive permissions in this app, and there will never be. The app is completely open-source (GPLv3). + +* Works offline and respects your privacy. +Loop doesn't require an Internet connection or online account registration. Your confidential data is never sent to anyone. Neither the developers nor any third-parties have access to it. ## Installing @@ -85,33 +83,33 @@ contribute, even if you are not a software developer. - Copyright (C) 2016-2019 Álinson Santos Xavier - - Loop Habit Tracker is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by the - Free Software Foundation, either version 3 of the License, or (at your - option) any later version. - - Loop Habit Tracker is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - more details. - - You should have received a copy of the GNU General Public License along - with this program. If not, see . - -[screen1]: screenshots/original/uhabits1.png -[screen2]: screenshots/original/uhabits2.png -[screen3]: screenshots/original/uhabits3.png -[screen4]: screenshots/original/uhabits4.png -[screen5]: screenshots/original/uhabits5.png -[screen6]: screenshots/original/uhabits6.png -[screen1th]: screenshots/thumbs/uhabits1.png -[screen2th]: screenshots/thumbs/uhabits2.png -[screen3th]: screenshots/thumbs/uhabits3.png -[screen4th]: screenshots/thumbs/uhabits4.png -[screen5th]: screenshots/thumbs/uhabits5.png -[screen6th]: screenshots/thumbs/uhabits6.png + Copyright (C) 2016-2019 Álinson Santos Xavier + + Loop Habit Tracker is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + Loop Habit Tracker is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along + with this program. If not, see . + +[screen1]: screenshots/uhabits1.png +[screen2]: screenshots/uhabits2.png +[screen3]: screenshots/uhabits3.png +[screen4]: screenshots/uhabits4.png +[screen5]: screenshots/uhabits5.png +[screen6]: screenshots/uhabits6.png +[screen1th]: screenshots/uhabits1_th.png +[screen2th]: screenshots/uhabits2_th.png +[screen3th]: screenshots/uhabits3_th.png +[screen4th]: screenshots/uhabits4_th.png +[screen5th]: screenshots/uhabits5_th.png +[screen6th]: screenshots/uhabits6_th.png [poedit]: http://translate.loophabits.org [playstore]: https://play.google.com/store/apps/details?id=org.isoron.uhabits [releases]: https://github.com/iSoron/uhabits/releases diff --git a/android/.gitignore b/android/.gitignore index 9ad9f8ab3..29f35cce8 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -13,6 +13,7 @@ .gradle .idea .project +.secret Thumbs.db art/ bin/ diff --git a/android/android-base/build.gradle b/android/android-base/build.gradle index a86283c2d..f10169233 100644 --- a/android/android-base/build.gradle +++ b/android/android-base/build.gradle @@ -1,23 +1,14 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' android { compileSdkVersion COMPILE_SDK_VERSION as Integer - buildToolsVersion BUILD_TOOLS_VERSION defaultConfig { minSdkVersion MIN_SDK_VERSION as Integer targetSdkVersion TARGET_SDK_VERSION as Integer - versionCode 1 - versionName "1.0" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } + versionCode VERSION_CODE as Integer + versionName "$VERSION_NAME" } compileOptions { @@ -29,25 +20,14 @@ android { checkReleaseBuilds false abortOnError false } - - buildToolsVersion '26.0.2' } dependencies { implementation "com.google.dagger:dagger:$DAGGER_VERSION" - implementation "com.android.support:design:$SUPPORT_LIBRARY_VERSION" - implementation "com.android.support:appcompat-v7:$SUPPORT_LIBRARY_VERSION" + implementation 'com.google.android.material:material:1.0.0' + implementation 'androidx.appcompat:appcompat:1.0.0' implementation "org.apache.commons:commons-lang3:3.5" annotationProcessor "com.google.dagger:dagger-compiler:$DAGGER_VERSION" - androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:$DAGGER_VERSION" - androidTestImplementation "com.google.dagger:dagger:$DAGGER_VERSION" - testAnnotationProcessor "com.google.dagger:dagger-compiler:$DAGGER_VERSION" - testImplementation "junit:junit:4.12" - - androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', { - exclude group: 'com.android.support', module: 'support-annotations' - }) - - + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$KOTLIN_VERSION" } diff --git a/android/android-base/src/main/java/org/isoron/androidbase/AndroidBugReporter.java b/android/android-base/src/main/java/org/isoron/androidbase/AndroidBugReporter.java deleted file mode 100644 index e50e2b04d..000000000 --- a/android/android-base/src/main/java/org/isoron/androidbase/AndroidBugReporter.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2017 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -package org.isoron.androidbase; - -import android.content.*; -import android.os.*; -import android.support.annotation.*; -import android.view.*; - -import java.io.*; -import java.text.*; -import java.util.*; - -import javax.inject.*; - -public class AndroidBugReporter -{ - private final Context context; - - @Inject - public AndroidBugReporter(@NonNull @AppContext Context context) - { - this.context = context; - } - - /** - * Captures and returns a bug report. The bug report contains some device - * information and the logcat. - * - * @return a String containing the bug report. - * @throws IOException when any I/O error occur. - */ - @NonNull - public String getBugReport() throws IOException - { - String logcat = getLogcat(); - String deviceInfo = getDeviceInfo(); - - String log = "---------- BUG REPORT BEGINS ----------\n"; - log += deviceInfo + "\n" + logcat; - log += "---------- BUG REPORT ENDS ------------\n"; - - return log; - } - - public String getDeviceInfo() - { - if (context == null) return "null context\n"; - - WindowManager wm = - (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - - return - String.format("App Version Name: %s\n", BuildConfig.VERSION_NAME) + - String.format("App Version Code: %s\n", BuildConfig.VERSION_CODE) + - String.format("OS Version: %s (%s)\n", - System.getProperty("os.version"), Build.VERSION.INCREMENTAL) + - String.format("OS API Level: %s\n", Build.VERSION.SDK) + - String.format("Device: %s\n", Build.DEVICE) + - String.format("Model (Product): %s (%s)\n", Build.MODEL, - Build.PRODUCT) + - String.format("Manufacturer: %s\n", Build.MANUFACTURER) + - String.format("Other tags: %s\n", Build.TAGS) + - String.format("Screen Width: %s\n", - wm.getDefaultDisplay().getWidth()) + - String.format("Screen Height: %s\n", - wm.getDefaultDisplay().getHeight()) + - String.format("External storage state: %s\n\n", - Environment.getExternalStorageState()); - } - - public String getLogcat() throws IOException - { - int maxLineCount = 250; - StringBuilder builder = new StringBuilder(); - - String[] command = new String[]{ "logcat", "-d" }; - java.lang.Process process = Runtime.getRuntime().exec(command); - - InputStreamReader in = new InputStreamReader(process.getInputStream()); - BufferedReader bufferedReader = new BufferedReader(in); - - LinkedList log = new LinkedList<>(); - - String line; - while ((line = bufferedReader.readLine()) != null) - { - log.addLast(line); - if (log.size() > maxLineCount) log.removeFirst(); - } - - for (String l : log) - { - builder.append(l); - builder.append('\n'); - } - - return builder.toString(); - } - - /** - * Captures a bug report and saves it to a file in the SD card. - *

- * The contents of the file are generated by the method {@link - * #getBugReport()}. The file is saved in the apps's external private - * storage. - * - * @return the generated file. - * @throws IOException when I/O errors occur. - */ - @NonNull - public void dumpBugReportToFile() - { - try - { - - String date = - new SimpleDateFormat("yyyy-MM-dd HHmmss", Locale.US).format( - new Date()); - - if (context == null) throw new IllegalStateException(); - - File dir = new AndroidDirFinder(context).getFilesDir("Logs"); - if (dir == null) - throw new IOException("log dir should not be null"); - - File logFile = - new File(String.format("%s/Log %s.txt", dir.getPath(), date)); - FileWriter output = new FileWriter(logFile); - output.write(getBugReport()); - output.close(); - } - catch (IOException e) - { - e.printStackTrace(); - } - } -} diff --git a/android/android-base/src/main/java/org/isoron/androidbase/AndroidBugReporter.kt b/android/android-base/src/main/java/org/isoron/androidbase/AndroidBugReporter.kt new file mode 100644 index 000000000..ea013ceef --- /dev/null +++ b/android/android-base/src/main/java/org/isoron/androidbase/AndroidBugReporter.kt @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2017 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ +package org.isoron.androidbase + +import android.content.Context +import android.os.Build +import android.os.Environment +import android.view.WindowManager +import java.io.* +import java.text.SimpleDateFormat +import java.util.* +import javax.inject.Inject + +open class AndroidBugReporter @Inject constructor(@AppContext private val context: Context) { + + /** + * Captures and returns a bug report. The bug report contains some device + * information and the logcat. + * + * @return a String containing the bug report. + * @throws IOException when any I/O error occur. + */ + @Throws(IOException::class) + fun getBugReport(): String { + var log = "---------- BUG REPORT BEGINS ----------\n" + log += "${getLogcat()}\n" + log += "${getDeviceInfo()}\n" + log += "---------- BUG REPORT ENDS ------------\n" + return log + } + + @Throws(IOException::class) + fun getLogcat(): String { + val maxLineCount = 250 + val builder = StringBuilder() + val process = Runtime.getRuntime().exec(arrayOf("logcat", "-d")) + val inputReader = InputStreamReader(process.inputStream) + val bufferedReader = BufferedReader(inputReader) + val log = LinkedList() + var line: String? + while (true) { + line = bufferedReader.readLine() + if (line == null) break; + log.addLast(line) + if (log.size > maxLineCount) log.removeFirst() + } + for (l in log) { + builder.appendln(l) + } + return builder.toString() + } + + /** + * Captures a bug report and saves it to a file in the SD card. + * + * The contents of the file are generated by the method [ ][.getBugReport]. The file is saved + * in the apps's external private storage. + * + * @return the generated file. + * @throws IOException when I/O errors occur. + */ + fun dumpBugReportToFile() { + try { + val date = SimpleDateFormat("yyyy-MM-dd HHmmss", Locale.US).format(Date()) + val dir = AndroidDirFinder(context).getFilesDir("Logs") + ?: throw IOException("log dir should not be null") + val logFile = File(String.format("%s/Log %s.txt", dir.path, date)) + val output = FileWriter(logFile) + output.write(getBugReport()) + output.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + + private fun getDeviceInfo(): String { + val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager + return buildString { + appendln("App Version Name: ${BuildConfig.VERSION_NAME}") + appendln("App Version Code: ${BuildConfig.VERSION_CODE}") + appendln("OS Version: ${System.getProperty("os.version")} (${Build.VERSION.INCREMENTAL})") + appendln("OS API Level: ${Build.VERSION.SDK}") + appendln("Device: ${Build.DEVICE}") + appendln("Model (Product): ${Build.MODEL} (${Build.PRODUCT})") + appendln("Manufacturer: ${Build.MANUFACTURER}") + appendln("Other tags: ${Build.TAGS}") + appendln("Screen Width: ${wm.defaultDisplay.width}") + appendln("Screen Height: ${wm.defaultDisplay.height}") + appendln("External storage state: ${Environment.getExternalStorageState()}") + appendln() + } + } + +} \ No newline at end of file diff --git a/android/android-base/src/main/java/org/isoron/androidbase/AndroidDirFinder.java b/android/android-base/src/main/java/org/isoron/androidbase/AndroidDirFinder.java deleted file mode 100644 index 3e40aed4d..000000000 --- a/android/android-base/src/main/java/org/isoron/androidbase/AndroidDirFinder.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2017 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -package org.isoron.androidbase; - -import android.content.*; -import android.support.annotation.*; -import android.support.v4.content.*; -import android.util.*; - -import org.isoron.androidbase.utils.*; - -import java.io.*; - -import javax.inject.*; - -public class AndroidDirFinder -{ - @NonNull - private Context context; - - @Inject - public AndroidDirFinder(@NonNull @AppContext Context context) - { - this.context = context; - } - - @Nullable - public File getFilesDir(@Nullable String relativePath) - { - File externalFilesDirs[] = - ContextCompat.getExternalFilesDirs(context, null); - if (externalFilesDirs == null) - { - Log.e("BaseSystem", - "getFilesDir: getExternalFilesDirs returned null"); - return null; - } - - return FileUtils.getDir(externalFilesDirs, relativePath); - } -} diff --git a/android/android-base/src/main/java/org/isoron/androidbase/AndroidDirFinder.kt b/android/android-base/src/main/java/org/isoron/androidbase/AndroidDirFinder.kt new file mode 100644 index 000000000..9dbeeeb65 --- /dev/null +++ b/android/android-base/src/main/java/org/isoron/androidbase/AndroidDirFinder.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2017 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ +package org.isoron.androidbase + +import android.content.Context +import androidx.core.content.ContextCompat +import org.isoron.androidbase.utils.FileUtils +import java.io.File +import javax.inject.Inject + +class AndroidDirFinder @Inject constructor(@param:AppContext private val context: Context) { + fun getFilesDir(relativePath: String?): File? { + return FileUtils.getDir( + ContextCompat.getExternalFilesDirs(context, null), + relativePath + ) + } +} \ No newline at end of file diff --git a/android/android-base/src/main/java/org/isoron/androidbase/AppContext.java b/android/android-base/src/main/java/org/isoron/androidbase/AppContext.kt similarity index 80% rename from android/android-base/src/main/java/org/isoron/androidbase/AppContext.java rename to android/android-base/src/main/java/org/isoron/androidbase/AppContext.kt index d7521e26d..3146da5e2 100644 --- a/android/android-base/src/main/java/org/isoron/androidbase/AppContext.java +++ b/android/android-base/src/main/java/org/isoron/androidbase/AppContext.kt @@ -16,16 +16,14 @@ * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ +package org.isoron.androidbase -package org.isoron.androidbase; - -import java.lang.annotation.*; - -import javax.inject.*; +import java.lang.annotation.Documented +import java.lang.annotation.Retention +import java.lang.annotation.RetentionPolicy +import javax.inject.Qualifier @Qualifier @Documented @Retention(RetentionPolicy.RUNTIME) -public @interface AppContext -{ -} +annotation class AppContext \ No newline at end of file diff --git a/android/android-base/src/main/java/org/isoron/androidbase/BaseExceptionHandler.java b/android/android-base/src/main/java/org/isoron/androidbase/BaseExceptionHandler.java deleted file mode 100644 index 3a04b8b4f..000000000 --- a/android/android-base/src/main/java/org/isoron/androidbase/BaseExceptionHandler.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2017 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -package org.isoron.androidbase; - -import android.support.annotation.*; - -import org.isoron.androidbase.activities.*; - -public class BaseExceptionHandler implements Thread.UncaughtExceptionHandler -{ - @Nullable - private Thread.UncaughtExceptionHandler originalHandler; - - @NonNull - private BaseActivity activity; - - public BaseExceptionHandler(@NonNull BaseActivity activity) - { - this.activity = activity; - originalHandler = Thread.getDefaultUncaughtExceptionHandler(); - } - - @Override - public void uncaughtException(@Nullable Thread thread, - @Nullable Throwable ex) - { - if (ex == null) return; - - try - { - ex.printStackTrace(); - new AndroidBugReporter(activity).dumpBugReportToFile(); - } - catch (Exception e) - { - e.printStackTrace(); - } - -// if (ex.getCause() instanceof InconsistentDatabaseException) -// { -// HabitsApplication app = (HabitsApplication) activity.getApplication(); -// HabitList habits = app.getComponent().getHabitList(); -// habits.repair(); -// System.exit(0); -// } - - if (originalHandler != null) - originalHandler.uncaughtException(thread, ex); - } -} diff --git a/android/android-base/src/main/java/org/isoron/androidbase/BaseExceptionHandler.kt b/android/android-base/src/main/java/org/isoron/androidbase/BaseExceptionHandler.kt new file mode 100644 index 000000000..c6907f3ec --- /dev/null +++ b/android/android-base/src/main/java/org/isoron/androidbase/BaseExceptionHandler.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ +package org.isoron.androidbase + +import org.isoron.androidbase.activities.BaseActivity + +class BaseExceptionHandler(private val activity: BaseActivity) : Thread.UncaughtExceptionHandler { + + private val originalHandler: Thread.UncaughtExceptionHandler? = + Thread.getDefaultUncaughtExceptionHandler() + + override fun uncaughtException(thread: Thread?, ex: Throwable?) { + if (ex == null) return + if (thread == null) return + try { + ex.printStackTrace() + AndroidBugReporter(activity).dumpBugReportToFile() + } catch (e: Exception) { + e.printStackTrace() + } + originalHandler?.uncaughtException(thread, ex) + } +} \ No newline at end of file diff --git a/android/android-base/src/main/java/org/isoron/androidbase/SSLContextProvider.java b/android/android-base/src/main/java/org/isoron/androidbase/SSLContextProvider.java deleted file mode 100644 index c488bbc16..000000000 --- a/android/android-base/src/main/java/org/isoron/androidbase/SSLContextProvider.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2017 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -package org.isoron.androidbase; - -import android.content.*; -import android.support.annotation.*; - -import org.isoron.androidbase.*; - -import java.io.*; -import java.security.*; -import java.security.cert.Certificate; -import java.security.cert.*; - -import javax.inject.*; -import javax.net.ssl.*; - -public class SSLContextProvider -{ - private Context context; - - @Inject - public SSLContextProvider(@NonNull @AppContext Context context) - { - this.context = context; - } - - public SSLContext getCACertSSLContext() - { - try - { - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - InputStream caInput = context.getAssets().open("cacert.pem"); - Certificate ca = cf.generateCertificate(caInput); - - KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); - ks.load(null, null); - ks.setCertificateEntry("ca", ca); - - TrustManagerFactory tmf = TrustManagerFactory.getInstance( - TrustManagerFactory.getDefaultAlgorithm()); - tmf.init(ks); - - SSLContext ctx = SSLContext.getInstance("TLS"); - ctx.init(null, tmf.getTrustManagers(), null); - - return ctx; - } - catch (Exception e) - { - throw new RuntimeException(e); - } - } -} diff --git a/android/android-base/src/main/java/org/isoron/androidbase/SSLContextProvider.kt b/android/android-base/src/main/java/org/isoron/androidbase/SSLContextProvider.kt new file mode 100644 index 000000000..797d6c93d --- /dev/null +++ b/android/android-base/src/main/java/org/isoron/androidbase/SSLContextProvider.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ +package org.isoron.androidbase + +import android.content.Context +import java.security.KeyStore +import java.security.cert.CertificateFactory +import javax.inject.Inject +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManagerFactory + +class SSLContextProvider @Inject constructor(@param:AppContext private val context: Context) { + fun getCACertSSLContext(): SSLContext { + try { + val cf = CertificateFactory.getInstance("X.509") + val ca = cf.generateCertificate(context.assets.open("cacert.pem")) + val ks = KeyStore.getInstance(KeyStore.getDefaultType()).apply { + load(null, null) + setCertificateEntry("ca", ca) + } + val alg = TrustManagerFactory.getDefaultAlgorithm() + val tmf = TrustManagerFactory.getInstance(alg).apply { + init(ks) + } + return SSLContext.getInstance("TLS").apply { + init(null, tmf.trustManagers, null) + } + } catch (e: Exception) { + throw RuntimeException(e) + } + } +} \ No newline at end of file diff --git a/android/android-base/src/main/java/org/isoron/androidbase/activities/BaseActivity.java b/android/android-base/src/main/java/org/isoron/androidbase/activities/BaseActivity.java index fae8c82ba..2dea22e50 100644 --- a/android/android-base/src/main/java/org/isoron/androidbase/activities/BaseActivity.java +++ b/android/android-base/src/main/java/org/isoron/androidbase/activities/BaseActivity.java @@ -21,8 +21,9 @@ package org.isoron.androidbase.activities; import android.content.*; import android.os.*; -import android.support.annotation.*; -import android.support.v7.app.*; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.*; import android.view.*; import org.isoron.androidbase.*; diff --git a/android/android-base/src/main/java/org/isoron/androidbase/activities/BaseMenu.java b/android/android-base/src/main/java/org/isoron/androidbase/activities/BaseMenu.java index 7cba01ddc..c5113a3fa 100644 --- a/android/android-base/src/main/java/org/isoron/androidbase/activities/BaseMenu.java +++ b/android/android-base/src/main/java/org/isoron/androidbase/activities/BaseMenu.java @@ -19,9 +19,11 @@ package org.isoron.androidbase.activities; -import android.support.annotation.*; import android.view.*; +import androidx.annotation.MenuRes; +import androidx.annotation.NonNull; + /** * Base class for all the menus in the application. *

diff --git a/android/android-base/src/main/java/org/isoron/androidbase/activities/BaseRootView.java b/android/android-base/src/main/java/org/isoron/androidbase/activities/BaseRootView.java index 21c0322f7..3ff339387 100644 --- a/android/android-base/src/main/java/org/isoron/androidbase/activities/BaseRootView.java +++ b/android/android-base/src/main/java/org/isoron/androidbase/activities/BaseRootView.java @@ -20,8 +20,10 @@ package org.isoron.androidbase.activities; import android.content.*; -import android.support.annotation.*; -import android.support.v7.widget.Toolbar; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; import android.view.*; import android.widget.*; diff --git a/android/android-base/src/main/java/org/isoron/androidbase/activities/BaseScreen.java b/android/android-base/src/main/java/org/isoron/androidbase/activities/BaseScreen.java index f135a6e62..331d91742 100644 --- a/android/android-base/src/main/java/org/isoron/androidbase/activities/BaseScreen.java +++ b/android/android-base/src/main/java/org/isoron/androidbase/activities/BaseScreen.java @@ -24,15 +24,19 @@ import android.graphics.*; import android.graphics.drawable.*; import android.net.*; import android.os.*; -import android.support.annotation.*; -import android.support.design.widget.*; -import android.support.v4.content.res.*; -import android.support.v7.app.*; -import android.support.v7.view.ActionMode; -import android.support.v7.widget.Toolbar; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.core.content.res.*; +import androidx.appcompat.app.*; +import androidx.appcompat.view.ActionMode; +import androidx.appcompat.widget.Toolbar; import android.view.*; import android.widget.*; +import com.google.android.material.snackbar.Snackbar; + import org.isoron.androidbase.*; import org.isoron.androidbase.utils.*; @@ -40,7 +44,7 @@ import java.io.*; import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION_CODES.LOLLIPOP; -import static android.support.v4.content.FileProvider.getUriForFile; +import static androidx.core.content.FileProvider.getUriForFile; /** * Base class for all screens in the application. @@ -214,7 +218,7 @@ public class BaseScreen if (snackbar == null) { snackbar = Snackbar.make(rootView, stringId, Snackbar.LENGTH_SHORT); - int tvId = android.support.design.R.id.snackbar_text; + int tvId = R.id.snackbar_text; TextView tv = (TextView) snackbar.getView().findViewById(tvId); tv.setTextColor(Color.WHITE); } diff --git a/android/android-base/src/main/java/org/isoron/androidbase/activities/BaseSelectionMenu.java b/android/android-base/src/main/java/org/isoron/androidbase/activities/BaseSelectionMenu.java index 87396e216..a1b486555 100644 --- a/android/android-base/src/main/java/org/isoron/androidbase/activities/BaseSelectionMenu.java +++ b/android/android-base/src/main/java/org/isoron/androidbase/activities/BaseSelectionMenu.java @@ -19,8 +19,9 @@ package org.isoron.androidbase.activities; -import android.support.annotation.*; -import android.support.v7.view.ActionMode; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.view.ActionMode; import android.view.*; /** diff --git a/android/android-base/src/main/java/org/isoron/androidbase/utils/FileUtils.java b/android/android-base/src/main/java/org/isoron/androidbase/utils/FileUtils.java index 59ca4a9b4..b04f969b8 100644 --- a/android/android-base/src/main/java/org/isoron/androidbase/utils/FileUtils.java +++ b/android/android-base/src/main/java/org/isoron/androidbase/utils/FileUtils.java @@ -20,9 +20,11 @@ package org.isoron.androidbase.utils; import android.os.*; -import android.support.annotation.*; import android.util.*; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import java.io.*; public abstract class FileUtils diff --git a/android/android-base/src/main/java/org/isoron/androidbase/utils/InterfaceUtils.java b/android/android-base/src/main/java/org/isoron/androidbase/utils/InterfaceUtils.java index e1fea418b..e9d484cb1 100644 --- a/android/android-base/src/main/java/org/isoron/androidbase/utils/InterfaceUtils.java +++ b/android/android-base/src/main/java/org/isoron/androidbase/utils/InterfaceUtils.java @@ -22,8 +22,10 @@ package org.isoron.androidbase.utils; import android.content.*; import android.content.res.*; import android.graphics.*; -import android.support.annotation.*; -import android.support.v4.view.*; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.view.*; import android.util.*; import android.view.*; import android.widget.*; diff --git a/android/android-base/src/main/java/org/isoron/androidbase/utils/StyledResources.java b/android/android-base/src/main/java/org/isoron/androidbase/utils/StyledResources.java index d15766f95..d00588a90 100644 --- a/android/android-base/src/main/java/org/isoron/androidbase/utils/StyledResources.java +++ b/android/android-base/src/main/java/org/isoron/androidbase/utils/StyledResources.java @@ -22,7 +22,9 @@ package org.isoron.androidbase.utils; import android.content.*; import android.content.res.*; import android.graphics.drawable.*; -import android.support.annotation.*; + +import androidx.annotation.AttrRes; +import androidx.annotation.NonNull; import org.isoron.androidbase.*; diff --git a/android/android-pickers/build.gradle b/android/android-pickers/build.gradle index 067d2a567..0627c16ca 100644 --- a/android/android-pickers/build.gradle +++ b/android/android-pickers/build.gradle @@ -2,24 +2,12 @@ apply plugin: 'com.android.library' android { compileSdkVersion COMPILE_SDK_VERSION as Integer - buildToolsVersion BUILD_TOOLS_VERSION defaultConfig { minSdkVersion MIN_SDK_VERSION as Integer targetSdkVersion TARGET_SDK_VERSION as Integer - versionCode 1 - versionName "1.0" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } - buildToolsVersion '26.0.2' - compileOptions { targetCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8 @@ -32,5 +20,5 @@ android { } dependencies { - implementation "com.android.support:appcompat-v7:$SUPPORT_LIBRARY_VERSION" + implementation 'androidx.appcompat:appcompat:1.0.0' } diff --git a/android/android-pickers/src/main/java/com/android/colorpicker/ColorPickerDialog.java b/android/android-pickers/src/main/java/com/android/colorpicker/ColorPickerDialog.java index 1060faf5b..a4f503392 100644 --- a/android/android-pickers/src/main/java/com/android/colorpicker/ColorPickerDialog.java +++ b/android/android-pickers/src/main/java/com/android/colorpicker/ColorPickerDialog.java @@ -18,8 +18,8 @@ package com.android.colorpicker; import android.app.*; import android.os.*; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.*; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.*; import android.view.*; import android.widget.*; diff --git a/android/android-pickers/src/main/java/com/android/datetimepicker/date/MonthView.java b/android/android-pickers/src/main/java/com/android/datetimepicker/date/MonthView.java index eff8b8186..79cda6b80 100644 --- a/android/android-pickers/src/main/java/com/android/datetimepicker/date/MonthView.java +++ b/android/android-pickers/src/main/java/com/android/datetimepicker/date/MonthView.java @@ -21,13 +21,15 @@ import android.content.res.*; import android.graphics.*; import android.graphics.Paint.*; import android.os.*; -import android.support.v4.view.*; -import android.support.v4.view.accessibility.*; -import android.support.v4.widget.*; +import androidx.core.view.*; +import androidx.core.view.accessibility.*; +import androidx.core.widget.*; import android.text.format.*; import android.view.*; import android.view.accessibility.*; +import androidx.customview.widget.ExploreByTouchHelper; + import com.android.*; import com.android.datetimepicker.*; import com.android.datetimepicker.date.MonthAdapter.*; diff --git a/android/android-pickers/src/main/java/com/android/datetimepicker/time/AmPmCirclesView.java b/android/android-pickers/src/main/java/com/android/datetimepicker/time/AmPmCirclesView.java index f310a1d85..3c6316521 100644 --- a/android/android-pickers/src/main/java/com/android/datetimepicker/time/AmPmCirclesView.java +++ b/android/android-pickers/src/main/java/com/android/datetimepicker/time/AmPmCirclesView.java @@ -41,8 +41,8 @@ public class AmPmCirclesView extends View { private final Paint mPaint = new Paint(); private int mSelectedAlpha; private int mUnselectedColor; - private int mAmPmTextColor; - private int mSelectedColor; + protected int mAmPmTextColor = Color.WHITE; + protected int mSelectedColor = Color.BLUE; private float mCircleRadiusMultiplier; private float mAmPmCircleRadiusMultiplier; private String mAmText; @@ -73,8 +73,8 @@ public class AmPmCirclesView extends View { Resources res = context.getResources(); mUnselectedColor = res.getColor(R.color.white); - mSelectedColor = res.getColor(R.color.blue); - mAmPmTextColor = res.getColor(R.color.ampm_text_color); + //mSelectedColor = res.getColor(R.color.blue); + //mAmPmTextColor = res.getColor(R.color.ampm_text_color); mSelectedAlpha = SELECTED_ALPHA; String typefaceFamily = res.getString(R.string.sans_serif); Typeface tf = Typeface.create(typefaceFamily, Typeface.NORMAL); @@ -105,8 +105,8 @@ public class AmPmCirclesView extends View { mSelectedAlpha = SELECTED_ALPHA_THEME_DARK; } else { mUnselectedColor = res.getColor(R.color.white); - mSelectedColor = res.getColor(R.color.blue); - mAmPmTextColor = res.getColor(R.color.ampm_text_color); + //mSelectedColor = res.getColor(R.color.blue); + //mAmPmTextColor = res.getColor(R.color.ampm_text_color); mSelectedAlpha = SELECTED_ALPHA; } } diff --git a/android/android-pickers/src/main/java/com/android/datetimepicker/time/RadialPickerLayout.java b/android/android-pickers/src/main/java/com/android/datetimepicker/time/RadialPickerLayout.java index 265b98cf8..307424a3e 100644 --- a/android/android-pickers/src/main/java/com/android/datetimepicker/time/RadialPickerLayout.java +++ b/android/android-pickers/src/main/java/com/android/datetimepicker/time/RadialPickerLayout.java @@ -84,6 +84,14 @@ public class RadialPickerLayout extends FrameLayout implements OnTouchListener { private AnimatorSet mTransition; private Handler mHandler = new Handler(); + public void setColor(int selectedColor) + { + mHourRadialSelectorView.mPaint.setColor(selectedColor); + mMinuteRadialSelectorView.mPaint.setColor(selectedColor); + mAmPmCirclesView.mSelectedColor = selectedColor; + mAmPmCirclesView.mAmPmTextColor = selectedColor; + } + public interface OnValueSelectedListener { void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance); } diff --git a/android/android-pickers/src/main/java/com/android/datetimepicker/time/RadialSelectorView.java b/android/android-pickers/src/main/java/com/android/datetimepicker/time/RadialSelectorView.java index 48e4385b5..018147efa 100644 --- a/android/android-pickers/src/main/java/com/android/datetimepicker/time/RadialSelectorView.java +++ b/android/android-pickers/src/main/java/com/android/datetimepicker/time/RadialSelectorView.java @@ -40,7 +40,7 @@ public class RadialSelectorView extends View { // Alpha level for the line. private static final int FULL_ALPHA = Utils.FULL_ALPHA; - private final Paint mPaint = new Paint(); + protected final Paint mPaint = new Paint(); private boolean mIsInitialized; private boolean mDrawValuesReady; @@ -96,8 +96,6 @@ public class RadialSelectorView extends View { Resources res = context.getResources(); - int blue = res.getColor(R.color.blue); - mPaint.setColor(blue); mPaint.setAntiAlias(true); mSelectionAlpha = SELECTED_ALPHA; @@ -139,15 +137,11 @@ public class RadialSelectorView extends View { /* package */ void setTheme(Context context, boolean themeDark) { Resources res = context.getResources(); - int color; if (themeDark) { - color = res.getColor(R.color.red); mSelectionAlpha = SELECTED_ALPHA_THEME_DARK; } else { - color = res.getColor(R.color.blue); mSelectionAlpha = SELECTED_ALPHA; } - mPaint.setColor(color); } /** diff --git a/android/android-pickers/src/main/java/com/android/datetimepicker/time/TimePickerDialog.java b/android/android-pickers/src/main/java/com/android/datetimepicker/time/TimePickerDialog.java index 06c121b3c..0a8411bc5 100644 --- a/android/android-pickers/src/main/java/com/android/datetimepicker/time/TimePickerDialog.java +++ b/android/android-pickers/src/main/java/com/android/datetimepicker/time/TimePickerDialog.java @@ -23,7 +23,9 @@ import android.app.*; import android.content.*; import android.content.res.*; import android.os.*; -import android.support.v7.app.*; + +import androidx.appcompat.app.*; + import android.util.*; import android.view.*; import android.view.View.*; @@ -39,7 +41,8 @@ import java.util.*; /** * Dialog to set a time. */ -public class TimePickerDialog extends AppCompatDialogFragment implements OnValueSelectedListener{ +public class TimePickerDialog extends AppCompatDialogFragment implements OnValueSelectedListener +{ private static final String TAG = "TimePickerDialog"; private static final String KEY_HOUR_OF_DAY = "hour_of_day"; @@ -49,6 +52,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue private static final String KEY_IN_KB_MODE = "in_kb_mode"; private static final String KEY_TYPED_TIMES = "typed_times"; private static final String KEY_DARK_THEME = "dark_theme"; + private static final String KEY_SELECTED_COLOR = "selected_color"; public static final int HOUR_INDEX = 0; public static final int MINUTE_INDEX = 1; @@ -108,37 +112,50 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue * The callback interface used to indicate the user is done filling in * the time (they clicked on the 'Set' button). */ - public interface OnTimeSetListener { + public interface OnTimeSetListener + { /** - * @param view The view associated with this listener. + * @param view The view associated with this listener. * @param hourOfDay The hour that was set. - * @param minute The minute that was set. + * @param minute The minute that was set. */ void onTimeSet(RadialPickerLayout view, int hourOfDay, int minute); - - default void onTimeCleared(RadialPickerLayout view) {} + + default void onTimeCleared(RadialPickerLayout view) + { + } } - public TimePickerDialog() { + public TimePickerDialog() + { // Empty constructor required for dialog fragment. } @SuppressLint("Java") public TimePickerDialog(Context context, int theme, OnTimeSetListener callback, - int hourOfDay, int minute, boolean is24HourMode) { + int hourOfDay, int minute, boolean is24HourMode) + { // Empty constructor required for dialog fragment. } public static TimePickerDialog newInstance(OnTimeSetListener callback, - int hourOfDay, int minute, boolean is24HourMode) { + int hourOfDay, + int minute, + boolean is24HourMode, + int color) + { TimePickerDialog ret = new TimePickerDialog(); - ret.initialize(callback, hourOfDay, minute, is24HourMode); + ret.initialize(callback, hourOfDay, minute, is24HourMode, color); return ret; } public void initialize(OnTimeSetListener callback, - int hourOfDay, int minute, boolean is24HourMode) { + int hourOfDay, + int minute, + boolean is24HourMode, + int color) + { mCallback = callback; mInitialHourOfDay = hourOfDay; @@ -146,40 +163,47 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue mIs24HourMode = is24HourMode; mInKbMode = false; mThemeDark = false; + mSelectedColor = color; } /** * Set a dark or light theme. NOTE: this will only take effect for the next onCreateView. */ - public void setThemeDark(boolean dark) { + public void setThemeDark(boolean dark) + { mThemeDark = dark; } - public boolean isThemeDark() { + public boolean isThemeDark() + { return mThemeDark; } - public void setOnTimeSetListener(OnTimeSetListener callback) { + public void setOnTimeSetListener(OnTimeSetListener callback) + { mCallback = callback; } - public void setStartTime(int hourOfDay, int minute) { + public void setStartTime(int hourOfDay, int minute) + { mInitialHourOfDay = hourOfDay; mInitialMinute = minute; mInKbMode = false; } @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(Bundle savedInstanceState) + { super.onCreate(savedInstanceState); if (savedInstanceState != null && savedInstanceState.containsKey(KEY_HOUR_OF_DAY) - && savedInstanceState.containsKey(KEY_MINUTE) - && savedInstanceState.containsKey(KEY_IS_24_HOUR_VIEW)) { + && savedInstanceState.containsKey(KEY_MINUTE) + && savedInstanceState.containsKey(KEY_IS_24_HOUR_VIEW)) { mInitialHourOfDay = savedInstanceState.getInt(KEY_HOUR_OF_DAY); mInitialMinute = savedInstanceState.getInt(KEY_MINUTE); mIs24HourMode = savedInstanceState.getBoolean(KEY_IS_24_HOUR_VIEW); mInKbMode = savedInstanceState.getBoolean(KEY_IN_KB_MODE); mThemeDark = savedInstanceState.getBoolean(KEY_DARK_THEME); + mSelectedColor = savedInstanceState.getInt(KEY_SELECTED_COLOR); } } @@ -191,7 +215,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + Bundle savedInstanceState) + { getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE); View view = inflater.inflate(R.layout.time_picker_dialog, null); @@ -203,8 +228,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue mSelectHours = res.getString(R.string.select_hours); mMinutePickerDescription = res.getString(R.string.minute_picker_description); mSelectMinutes = res.getString(R.string.select_minutes); - mSelectedColor = res.getColor(mThemeDark? R.color.red : R.color.blue); - mUnselectedColor = res.getColor(mThemeDark? R.color.white : R.color.numbers_text_color); + //mSelectedColor = res.getColor(mThemeDark ? R.color.red : R.color.blue); + mUnselectedColor = res.getColor(mThemeDark ? R.color.white : R.color.numbers_text_color); mHourView = (TextView) view.findViewById(R.id.hours); mHourView.setOnKeyListener(keyboardListener); @@ -223,8 +248,9 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue mTimePicker = (RadialPickerLayout) view.findViewById(R.id.time_picker); mTimePicker.setOnValueSelectedListener(this); mTimePicker.setOnKeyListener(keyboardListener); + mTimePicker.setColor(mSelectedColor); mTimePicker.initialize(getActivity(), mHapticFeedbackController, mInitialHourOfDay, - mInitialMinute, mIs24HourMode); + mInitialMinute, mIs24HourMode); int currentItemShowing = HOUR_INDEX; if (savedInstanceState != null && @@ -234,25 +260,31 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue setCurrentItemShowing(currentItemShowing, false, true, true); mTimePicker.invalidate(); - mHourView.setOnClickListener(new OnClickListener() { + mHourView.setOnClickListener(new OnClickListener() + { @Override - public void onClick(View v) { + public void onClick(View v) + { setCurrentItemShowing(HOUR_INDEX, true, false, true); tryVibrate(); } }); - mMinuteView.setOnClickListener(new OnClickListener() { + mMinuteView.setOnClickListener(new OnClickListener() + { @Override - public void onClick(View v) { + public void onClick(View v) + { setCurrentItemShowing(MINUTE_INDEX, true, false, true); tryVibrate(); } }); mDoneButton = (TextView) view.findViewById(R.id.done_button); - mDoneButton.setOnClickListener(new OnClickListener() { + mDoneButton.setOnClickListener(new OnClickListener() + { @Override - public void onClick(View v) { + public void onClick(View v) + { if (mInKbMode && isTypedTimeFullyLegal()) { finishKbMode(false); } else { @@ -260,25 +292,25 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue } if (mCallback != null) { mCallback.onTimeSet(mTimePicker, - mTimePicker.getHours(), mTimePicker.getMinutes()); + mTimePicker.getHours(), mTimePicker.getMinutes()); } dismiss(); } }); mDoneButton.setOnKeyListener(keyboardListener); - + mClearButton = (TextView) view.findViewById(R.id.clear_button); mClearButton.setOnClickListener(new OnClickListener() - { - @Override - public void onClick(View v) - { - if(mCallback != null) { - mCallback.onTimeCleared(mTimePicker); - } - dismiss(); - } - }); + { + @Override + public void onClick(View v) + { + if (mCallback != null) { + mCallback.onTimeCleared(mTimePicker); + } + dismiss(); + } + }); mClearButton.setOnKeyListener(keyboardListener); // Enable or disable the AM/PM view. @@ -293,15 +325,17 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue separatorView.setLayoutParams(paramsSeparator); } else { mAmPmTextView.setVisibility(View.VISIBLE); - updateAmPmDisplay(mInitialHourOfDay < 12? AM : PM); - mAmPmHitspace.setOnClickListener(new OnClickListener() { + updateAmPmDisplay(mInitialHourOfDay < 12 ? AM : PM); + mAmPmHitspace.setOnClickListener(new OnClickListener() + { @Override - public void onClick(View v) { + public void onClick(View v) + { tryVibrate(); int amOrPm = mTimePicker.getIsCurrentlyAmOrPm(); if (amOrPm == AM) { amOrPm = PM; - } else if (amOrPm == PM){ + } else if (amOrPm == PM) { amOrPm = AM; } updateAmPmDisplay(amOrPm); @@ -328,56 +362,61 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue mTypedTimes = new ArrayList(); } - // Set the theme at the end so that the initialize()s above don't counteract the theme. - mTimePicker.setTheme(getActivity().getApplicationContext(), mThemeDark); - // Prepare some palette to use. - int white = res.getColor(R.color.white); - int circleBackground = res.getColor(R.color.circle_background); - int line = res.getColor(R.color.line_background); - int timeDisplay = res.getColor(R.color.numbers_text_color); - ColorStateList doneTextColor = res.getColorStateList(R.color.done_text_color); - int doneBackground = R.drawable.done_background_color; - int darkGray = res.getColor(R.color.dark_gray); - int lightGray = res.getColor(R.color.light_gray); - int darkLine = res.getColor(R.color.line_dark); - ColorStateList darkDoneTextColor = res.getColorStateList(R.color.done_text_color_dark); - int darkDoneBackground = R.drawable.done_background_color_dark; +// // Set the theme at the end so that the initialize()s above don't counteract the theme. +// mTimePicker.setTheme(getActivity().getApplicationContext(), mThemeDark); +// // Prepare some palette to use. +// int white = res.getColor(R.color.white); +// int circleBackground = res.getColor(R.color.circle_background); +// int line = res.getColor(R.color.line_background); +// int timeDisplay = res.getColor(R.color.numbers_text_color); +// ColorStateList doneTextColor = res.getColorStateList(R.color.done_text_color); +// int doneBackground = R.drawable.done_background_color; +// +// int darkGray = res.getColor(R.color.dark_gray); +// int lightGray = res.getColor(R.color.light_gray); +// int darkLine = res.getColor(R.color.line_dark); +// ColorStateList darkDoneTextColor = res.getColorStateList(R.color.done_text_color_dark); +// int darkDoneBackground = R.drawable.done_background_color_dark; // Set the palette for each view based on the theme. - view.findViewById(R.id.time_display_background).setBackgroundColor(mThemeDark? darkGray : white); - view.findViewById(R.id.time_display).setBackgroundColor(mThemeDark? darkGray : white); - ((TextView) view.findViewById(R.id.separator)).setTextColor(mThemeDark? white : timeDisplay); - ((TextView) view.findViewById(R.id.ampm_label)).setTextColor(mThemeDark? white : timeDisplay); - view.findViewById(R.id.line).setBackgroundColor(mThemeDark? darkLine : line); - mDoneButton.setTextColor(mThemeDark? darkDoneTextColor : doneTextColor); - mTimePicker.setBackgroundColor(mThemeDark? lightGray : circleBackground); - mDoneButton.setBackgroundResource(mThemeDark? darkDoneBackground : doneBackground); +// view.findViewById(R.id.time_display_background).setBackgroundColor(mThemeDark? darkGray : white); +// view.findViewById(R.id.time_display).setBackgroundColor(mThemeDark? darkGray : white); +// ((TextView) view.findViewById(R.id.separator)).setTextColor(mThemeDark? white : timeDisplay); +// ((TextView) view.findViewById(R.id.ampm_label)).setTextColor(mThemeDark? white : timeDisplay); +// view.findViewById(R.id.line).setBackgroundColor(mThemeDark? darkLine : line); +// mDoneButton.setTextColor(mThemeDark? darkDoneTextColor : doneTextColor); +// mTimePicker.setBackgroundColor(mThemeDark? lightGray : circleBackground); +// mDoneButton.setBackgroundResource(mThemeDark? darkDoneBackground : doneBackground); return view; } @Override - public void onResume() { + public void onResume() + { super.onResume(); mHapticFeedbackController.start(); } @Override - public void onPause() { + public void onPause() + { super.onPause(); mHapticFeedbackController.stop(); } - public void tryVibrate() { + public void tryVibrate() + { mHapticFeedbackController.tryVibrate(); } - private void updateAmPmDisplay(int amOrPm) { + private void updateAmPmDisplay(int amOrPm) + { if (amOrPm == AM) { mAmPmTextView.setText(mAmText); Utils.tryAccessibilityAnnounce(mTimePicker, mAmText); mAmPmHitspace.setContentDescription(mAmText); - } else if (amOrPm == PM){ + } else if (amOrPm == PM) { mAmPmTextView.setText(mPmText); Utils.tryAccessibilityAnnounce(mTimePicker, mPmText); mAmPmHitspace.setContentDescription(mPmText); @@ -387,7 +426,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue } @Override - public void onSaveInstanceState(Bundle outState) { + public void onSaveInstanceState(Bundle outState) + { if (mTimePicker != null) { outState.putInt(KEY_HOUR_OF_DAY, mTimePicker.getHours()); outState.putInt(KEY_MINUTE, mTimePicker.getMinutes()); @@ -398,6 +438,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue outState.putIntegerArrayList(KEY_TYPED_TIMES, mTypedTimes); } outState.putBoolean(KEY_DARK_THEME, mThemeDark); + outState.putInt(KEY_SELECTED_COLOR, mSelectedColor); } } @@ -405,7 +446,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue * Called by the picker for updating the header display. */ @Override - public void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance) { + public void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance) + { if (pickerIndex == HOUR_INDEX) { setHour(newValue, false); String announcement = String.format("%d", newValue); @@ -417,7 +459,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue } Utils.tryAccessibilityAnnounce(mTimePicker, announcement); - } else if (pickerIndex == MINUTE_INDEX){ + } else if (pickerIndex == MINUTE_INDEX) { setMinute(newValue); mTimePicker.setContentDescription(mMinutePickerDescription + ": " + newValue); } else if (pickerIndex == AMPM_INDEX) { @@ -430,7 +472,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue } } - private void setHour(int value, boolean announce) { + private void setHour(int value, boolean announce) + { String format; if (mIs24HourMode) { format = "%02d"; @@ -450,7 +493,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue } } - private void setMinute(int value) { + private void setMinute(int value) + { if (value == 60) { value = 0; } @@ -462,7 +506,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue // Show either Hours or Minutes. private void setCurrentItemShowing(int index, boolean animateCircle, boolean delayLabelAnimate, - boolean announce) { + boolean announce) + { mTimePicker.setCurrentItemShowing(index, animateCircle); TextView labelToAnimate; @@ -485,8 +530,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue labelToAnimate = mMinuteView; } - int hourColor = (index == HOUR_INDEX)? mSelectedColor : mUnselectedColor; - int minuteColor = (index == MINUTE_INDEX)? mSelectedColor : mUnselectedColor; + int hourColor = (index == HOUR_INDEX) ? mSelectedColor : mUnselectedColor; + int minuteColor = (index == MINUTE_INDEX) ? mSelectedColor : mUnselectedColor; mHourView.setTextColor(hourColor); mMinuteView.setTextColor(minuteColor); @@ -499,15 +544,17 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue /** * For keyboard mode, processes key events. + * * @param keyCode the pressed key. * @return true if the key was successfully processed, false otherwise. */ - private boolean processKeyUp(int keyCode) { + private boolean processKeyUp(int keyCode) + { if (keyCode == KeyEvent.KEYCODE_ESCAPE || keyCode == KeyEvent.KEYCODE_BACK) { dismiss(); return true; } else if (keyCode == KeyEvent.KEYCODE_TAB) { - if(mInKbMode) { + if (mInKbMode) { if (isTypedTimeFullyLegal()) { finishKbMode(true); } @@ -522,7 +569,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue } if (mCallback != null) { mCallback.onTimeSet(mTimePicker, - mTimePicker.getHours(), mTimePicker.getMinutes()); + mTimePicker.getHours(), mTimePicker.getMinutes()); } dismiss(); return true; @@ -539,7 +586,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue deletedKeyStr = String.format("%d", getValFromKeyCode(deleted)); } Utils.tryAccessibilityAnnounce(mTimePicker, - String.format(mDeletedKeyFormat, deletedKeyStr)); + String.format(mDeletedKeyFormat, deletedKeyStr)); updateDisplay(true); } } @@ -549,7 +596,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue || keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7 || keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9 || (!mIs24HourMode && - (keyCode == getAmOrPmKeyCode(AM) || keyCode == getAmOrPmKeyCode(PM)))) { + (keyCode == getAmOrPmKeyCode(AM) || keyCode == getAmOrPmKeyCode(PM)))) { if (!mInKbMode) { if (mTimePicker == null) { // Something's wrong, because time picker should definitely not be null. @@ -572,11 +619,13 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue /** * Try to start keyboard mode with the specified key, as long as the timepicker is not in the * middle of a touch-event. + * * @param keyCode The key to use as the first press. Keyboard mode will not be started if the - * key is not legal to start with. Or, pass in -1 to get into keyboard mode without a starting - * key. + * key is not legal to start with. Or, pass in -1 to get into keyboard mode without a starting + * key. */ - private void tryStartingKbMode(int keyCode) { + private void tryStartingKbMode(int keyCode) + { if (mTimePicker.trySettingInputEnabled(false) && (keyCode == -1 || addKeyIfLegal(keyCode))) { mInKbMode = true; @@ -585,7 +634,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue } } - private boolean addKeyIfLegal(int keyCode) { + private boolean addKeyIfLegal(int keyCode) + { // If we're in 24hour mode, we'll need to check if the input is full. If in AM/PM mode, // we'll need to see if AM/PM have been typed. if ((mIs24HourMode && mTypedTimes.size() == 4) || @@ -617,7 +667,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue * Traverse the tree to see if the keys that have been typed so far are legal as is, * or may become legal as more keys are typed (excluding backspace). */ - private boolean isTypedTimeLegalSoFar() { + private boolean isTypedTimeLegalSoFar() + { Node node = mLegalTimesTree; for (int keyCode : mTypedTimes) { node = node.canReach(keyCode); @@ -631,7 +682,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue /** * Check if the time that has been typed so far is completely legal, as is. */ - private boolean isTypedTimeFullyLegal() { + private boolean isTypedTimeFullyLegal() + { if (mIs24HourMode) { // For 24-hour mode, the time is legal if the hours and minutes are each legal. Note: // getEnteredTime() will ONLY call isTypedTimeFullyLegal() when NOT in 24hour mode. @@ -645,7 +697,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue } } - private int deleteLastTypedKey() { + private int deleteLastTypedKey() + { int deleted = mTypedTimes.remove(mTypedTimes.size() - 1); if (!isTypedTimeFullyLegal()) { mDoneButton.setEnabled(false); @@ -655,9 +708,11 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue /** * Get out of keyboard mode. If there is nothing in typedTimes, revert to TimePicker's time. + * * @param changeDisplays If true, update the displays with the relevant time. */ - private void finishKbMode(boolean updateDisplays) { + private void finishKbMode(boolean updateDisplays) + { mInKbMode = false; if (!mTypedTimes.isEmpty()) { int values[] = getEnteredTime(null); @@ -677,29 +732,31 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue * Update the hours, minutes, and AM/PM displays with the typed times. If the typedTimes is * empty, either show an empty display (filled with the placeholder text), or update from the * timepicker's values. + * * @param allowEmptyDisplay if true, then if the typedTimes is empty, use the placeholder text. - * Otherwise, revert to the timepicker's values. + * Otherwise, revert to the timepicker's values. */ - private void updateDisplay(boolean allowEmptyDisplay) { + private void updateDisplay(boolean allowEmptyDisplay) + { if (!allowEmptyDisplay && mTypedTimes.isEmpty()) { int hour = mTimePicker.getHours(); int minute = mTimePicker.getMinutes(); setHour(hour, true); setMinute(minute); if (!mIs24HourMode) { - updateAmPmDisplay(hour < 12? AM : PM); + updateAmPmDisplay(hour < 12 ? AM : PM); } setCurrentItemShowing(mTimePicker.getCurrentItemShowing(), true, true, true); mDoneButton.setEnabled(true); } else { Boolean[] enteredZeros = {false, false}; int[] values = getEnteredTime(enteredZeros); - String hourFormat = enteredZeros[0]? "%02d" : "%2d"; - String minuteFormat = (enteredZeros[1])? "%02d" : "%2d"; - String hourStr = (values[0] == -1)? mDoublePlaceholderText : - String.format(hourFormat, values[0]).replace(' ', mPlaceholderText); - String minuteStr = (values[1] == -1)? mDoublePlaceholderText : - String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText); + String hourFormat = enteredZeros[0] ? "%02d" : "%2d"; + String minuteFormat = (enteredZeros[1]) ? "%02d" : "%2d"; + String hourStr = (values[0] == -1) ? mDoublePlaceholderText : + String.format(hourFormat, values[0]).replace(' ', mPlaceholderText); + String minuteStr = (values[1] == -1) ? mDoublePlaceholderText : + String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText); mHourView.setText(hourStr); mHourSpaceView.setText(hourStr); mHourView.setTextColor(mUnselectedColor); @@ -712,7 +769,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue } } - private static int getValFromKeyCode(int keyCode) { + private static int getValFromKeyCode(int keyCode) + { switch (keyCode) { case KeyEvent.KEYCODE_0: return 0; @@ -741,20 +799,22 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue /** * Get the currently-entered time, as integer values of the hours and minutes typed. + * * @param enteredZeros A size-2 boolean array, which the caller should initialize, and which - * may then be used for the caller to know whether zeros had been explicitly entered as either - * hours of minutes. This is helpful for deciding whether to show the dashes, or actual 0's. + * may then be used for the caller to know whether zeros had been explicitly entered as either + * hours of minutes. This is helpful for deciding whether to show the dashes, or actual 0's. * @return A size-3 int array. The first value will be the hours, the second value will be the * minutes, and the third will be either TimePickerDialog.AM or TimePickerDialog.PM. */ - private int[] getEnteredTime(Boolean[] enteredZeros) { + private int[] getEnteredTime(Boolean[] enteredZeros) + { int amOrPm = -1; int startIndex = 1; if (!mIs24HourMode && isTypedTimeFullyLegal()) { int keyCode = mTypedTimes.get(mTypedTimes.size() - 1); if (keyCode == getAmOrPmKeyCode(AM)) { amOrPm = AM; - } else if (keyCode == getAmOrPmKeyCode(PM)){ + } else if (keyCode == getAmOrPmKeyCode(PM)) { amOrPm = PM; } startIndex = 2; @@ -765,15 +825,15 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue int val = getValFromKeyCode(mTypedTimes.get(mTypedTimes.size() - i)); if (i == startIndex) { minute = val; - } else if (i == startIndex+1) { - minute += 10*val; + } else if (i == startIndex + 1) { + minute += 10 * val; if (enteredZeros != null && val == 0) { enteredZeros[1] = true; } - } else if (i == startIndex+2) { + } else if (i == startIndex + 2) { hour = val; - } else if (i == startIndex+3) { - hour += 10*val; + } else if (i == startIndex + 3) { + hour += 10 * val; if (enteredZeros != null && val == 0) { enteredZeros[0] = true; } @@ -787,7 +847,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue /** * Get the keycode value for AM and PM in the current language. */ - private int getAmOrPmKeyCode(int amOrPm) { + private int getAmOrPmKeyCode(int amOrPm) + { // Cache the codes. if (mAmKeyCode == -1 || mPmKeyCode == -1) { // Find the first character in the AM/PM text that is unique. @@ -822,7 +883,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue /** * Create a tree for deciding what keys can legally be typed. */ - private void generateLegalTimesTree() { + private void generateLegalTimesTree() + { // Create a quick cache of numbers to their keycodes. int k0 = KeyEvent.KEYCODE_0; int k1 = KeyEvent.KEYCODE_1; @@ -878,7 +940,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue // When the first digit is 2, the second digit may be 4-5. secondDigit = new Node(k4, k5); firstDigit.addChild(secondDigit); - // We must now be followd by the last minute digit. E.g. 2:40, 2:53. + // We must now be followed by the last minute digit. E.g. 2:40, 2:53. secondDigit.addChild(minuteSecondDigit); // The first digit may be 3-9. @@ -955,20 +1017,24 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue * mLegalKeys represents the keys that can be typed to get to the node. * mChildren are the children that can be reached from this node. */ - private class Node { + private class Node + { private int[] mLegalKeys; private ArrayList mChildren; - public Node(int... legalKeys) { + public Node(int... legalKeys) + { mLegalKeys = legalKeys; mChildren = new ArrayList(); } - public void addChild(Node child) { + public void addChild(Node child) + { mChildren.add(child); } - public boolean containsKey(int key) { + public boolean containsKey(int key) + { for (int i = 0; i < mLegalKeys.length; i++) { if (mLegalKeys[i] == key) { return true; @@ -977,7 +1043,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue return false; } - public Node canReach(int key) { + public Node canReach(int key) + { if (mChildren == null) { return null; } @@ -990,9 +1057,11 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue } } - private class KeyboardListener implements OnKeyListener { + private class KeyboardListener implements OnKeyListener + { @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { + public boolean onKey(View v, int keyCode, KeyEvent event) + { if (event.getAction() == KeyEvent.ACTION_UP) { return processKeyUp(keyCode); } @@ -1000,14 +1069,16 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue } } - public void setDismissListener( DialogInterface.OnDismissListener listener ) { + public void setDismissListener(DialogInterface.OnDismissListener listener) + { dismissListener = listener; } @Override - public void onDismiss(DialogInterface dialog) { + public void onDismiss(DialogInterface dialog) + { super.onDismiss(dialog); - if( dismissListener != null ) + if (dismissListener != null) dismissListener.onDismiss(dialog); } } diff --git a/android/android-pickers/src/main/res/layout/time_picker_dialog.xml b/android/android-pickers/src/main/res/layout/time_picker_dialog.xml index 44393dca6..cefb56099 100644 --- a/android/android-pickers/src/main/res/layout/time_picker_dialog.xml +++ b/android/android-pickers/src/main/res/layout/time_picker_dialog.xml @@ -49,33 +49,33 @@ android:layout_height="1dip" android:background="@color/line_background" /> - -