diff --git a/build.gradle b/build.gradle index 19f343658..20d17ca9c 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { maven { url 'https://maven.fabric.io/public' } } dependencies { - classpath 'com.android.tools.build:gradle:3.0.0-beta3' + classpath 'com.android.tools.build:gradle:3.0.0-beta6' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${KOTLIN}" classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' classpath 'com.github.triplet.gradle:play-publisher:1.2.0' diff --git a/core/README.md b/core/README.md index 4cb68d066..39899dd46 100644 --- a/core/README.md +++ b/core/README.md @@ -178,7 +178,7 @@ KAU's swipe is a Kotlin rewrite, along with support for all directions and weakl # Debounce Debouncing is a means of throttling a function so that it is called no more than once in a given instance of time. -An example where you'd like this behaviour is the searchview; you want to deliver search results quickly, +An example where you'd like this behaviour is the searchview: you want to deliver search results quickly, but you don't want to update your response with each new character. Instead, you can wait until a user finishes their query, then search for the results. @@ -210,7 +210,7 @@ Include your email and subject, along with other optional configurations such as ## Extension Functions > "[Extensions](https://kotlinlang.org/docs/reference/extensions.html) provide the ability to extend a class with new functionality without having to inherit from the class" -Note that since KAU depends on [ANKO](https://github.com/Kotlin/anko), all of the extensions in its core package is also in KAU. +
Note that since KAU depends on [ANKO](https://github.com/Kotlin/anko), all of the extensions in its core package is also in KAU. KAU's vast collection of extensions is one of its strongest features. There are too many to explain here, but you may check out the [utils package](https://github.com/AllanWang/KAU/tree/master/core/src/main/kotlin/ca/allanwang/kau/utils) diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt index 5631e7071..33bdc6284 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/ActivityUtils.kt @@ -32,9 +32,9 @@ fun Activity.startActivityForResult( bundle: Bundle? = null, intentBuilder: Intent.() -> Unit = {}) { val intent = Intent(this, clazz) - val fullBundle = if (transition && buildIsLollipopAndUp) - ActivityOptions.makeSceneTransitionAnimation(this).toBundle() - else Bundle() + val fullBundle = Bundle() + if (transition && buildIsLollipopAndUp) + fullBundle.with(ActivityOptions.makeSceneTransitionAnimation(this).toBundle()) if (bundle != null) fullBundle.putAll(bundle) intent.intentBuilder() startActivityForResult(intent, requestCode, if (fullBundle.isEmpty) null else fullBundle) diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt index a72c7dd56..0664dc679 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt @@ -43,9 +43,9 @@ fun Context.startActivity( intentBuilder: Intent.() -> Unit = {}) { val intent = Intent(this, clazz) if (clearStack) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) - val fullBundle = if (transition && this is Activity && buildIsLollipopAndUp) - ActivityOptions.makeSceneTransitionAnimation(this).toBundle() - else Bundle() + val fullBundle = Bundle() + if (transition && this is Activity && buildIsLollipopAndUp) + fullBundle.with(ActivityOptions.makeSceneTransitionAnimation(this).toBundle()) if (transition && this !is Activity) KL.d("Cannot make scene transition when context is not an instance of an Activity") if (bundle != null) fullBundle.putAll(bundle) intent.intentBuilder() @@ -57,9 +57,10 @@ fun Context.startActivity( * Bring in activity from the right */ fun Context.startActivitySlideIn(clazz: Class, clearStack: Boolean = false, intentBuilder: Intent.() -> Unit = {}, bundleBuilder: Bundle.() -> Unit = {}) { - val bundle = ActivityOptionsCompat.makeCustomAnimation(this, R.anim.kau_slide_in_right, R.anim.kau_fade_out).toBundle() - bundle.bundleBuilder() - startActivity(clazz, clearStack, intentBuilder = intentBuilder, bundle = bundle) + val fullBundle = Bundle() + fullBundle.with(ActivityOptionsCompat.makeCustomAnimation(this, R.anim.kau_slide_in_right, R.anim.kau_fade_out).toBundle()) + fullBundle.bundleBuilder() + startActivity(clazz, clearStack, intentBuilder = intentBuilder, bundle = if (fullBundle.isEmpty) null else fullBundle) } /** @@ -69,9 +70,10 @@ fun Context.startActivitySlideIn(clazz: Class, clearStack: Boolean * Consequently, the stack will be cleared by default */ fun Context.startActivitySlideOut(clazz: Class, clearStack: Boolean = true, intentBuilder: Intent.() -> Unit = {}, bundleBuilder: Bundle.() -> Unit = {}) { - val bundle = ActivityOptionsCompat.makeCustomAnimation(this, R.anim.kau_fade_in, R.anim.kau_slide_out_right_top).toBundle() - bundle.bundleBuilder() - startActivity(clazz, clearStack, intentBuilder = intentBuilder, bundle = bundle) + val fullBundle = Bundle() + fullBundle.with(ActivityOptionsCompat.makeCustomAnimation(this, R.anim.kau_fade_in, R.anim.kau_slide_out_right_top).toBundle()) + fullBundle.bundleBuilder() + startActivity(clazz, clearStack, intentBuilder = intentBuilder, bundle = if (fullBundle.isEmpty) null else fullBundle) } fun Context.startPlayStoreLink(@StringRes packageIdRes: Int) = startPlayStoreLink(string(packageIdRes)) diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt index 50a3c29b1..954bedc0a 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt @@ -6,6 +6,7 @@ import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable +import android.os.Bundle import android.os.Handler import android.os.Looper import android.support.annotation.IntRange @@ -53,14 +54,16 @@ annotation class KauUtils * Converts minute value to string * Whole hours and days will be converted as such, otherwise it will default to x minutes */ -@KauUtils fun Context.minuteToText(minutes: Long): String = with(minutes) { +@KauUtils +fun Context.minuteToText(minutes: Long): String = with(minutes) { if (this < 0L) string(R.string.kau_none) else if (this % 1440L == 0L) plural(R.plurals.kau_x_days, this / 1440L) else if (this % 60L == 0L) plural(R.plurals.kau_x_hours, this / 60L) else plural(R.plurals.kau_x_minutes, this) } -@KauUtils fun Number.round(@IntRange(from = 1L) decimalCount: Int): String { +@KauUtils +fun Number.round(@IntRange(from = 1L) decimalCount: Int): String { val expression = StringBuilder().append("#.") (1..decimalCount).forEach { expression.append("#") } val formatter = DecimalFormat(expression.toString()) @@ -72,7 +75,8 @@ annotation class KauUtils * Extracts the bitmap of a drawable, and applies a scale if given * For solid colors, a 1 x 1 pixel will be generated */ -@KauUtils fun Drawable.toBitmap(scaling: Float = 1f, config: Bitmap.Config = Bitmap.Config.ARGB_8888): Bitmap { +@KauUtils +fun Drawable.toBitmap(scaling: Float = 1f, config: Bitmap.Config = Bitmap.Config.ARGB_8888): Bitmap { if (this is BitmapDrawable && bitmap != null) { if (scaling == 1f) return bitmap val width = (bitmap.width * scaling).toInt() @@ -122,4 +126,12 @@ class KauException(message: String) : RuntimeException(message) fun String.withMaxLength(n: Int): String = if (length <= n) this - else substring(0, n-1) + KAU_ELLIPSIS \ No newline at end of file + else substring(0, n - 1) + KAU_ELLIPSIS + +/** + * Similar to [Bundle.putAll], but checks for a null insert and returns the parent bundle + */ +fun Bundle.with(bundle: Bundle?): Bundle { + if (bundle != null) putAll(bundle) + return this +} \ No newline at end of file diff --git a/core/src/main/res/values/ids.xml b/core/src/main/res/values/ids.xml index c4912e201..28ff9d93f 100644 --- a/core/src/main/res/values/ids.xml +++ b/core/src/main/res/values/ids.xml @@ -14,6 +14,7 @@ + diff --git a/docs/Changelog.md b/docs/Changelog.md index 4165fbbb5..6c5bb113f 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,5 +1,10 @@ # Changelog +## v3.4.1 +* Validate context before showing dialogs +* Add intent resolver checks prior to all executions. +* Fix bundle NPE when starting activity + ## v3.4.0 * Update to gradle 4.x; api and implementation rather than compile * Update dependencies diff --git a/gradle.properties b/gradle.properties index 08fad4dc6..9c2675917 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,7 +23,7 @@ TARGET_SDK=26 BUILD_TOOLS=26.0.1 ANDROID_SUPPORT_LIBS=26.1.0 -VERSION_NAME=3.4.0 +VERSION_NAME=3.4.3 KOTLIN=1.1.4-3 ABOUT_LIBRARIES=5.9.7 diff --git a/kpref-activity/README.md b/kpref-activity/README.md index a9aac8966..69d489206 100644 --- a/kpref-activity/README.md +++ b/kpref-activity/README.md @@ -30,6 +30,7 @@ Contract | Mandatory | Optional | Description `KPrefSeekbarContract` | `NA` | `min` `max` `increments` `toText` `textViewConfigs` | Addtional configurations for a seekbar, as well as text to be displayed on the side. `KPrefSubItemsContract` | `itemBuilder` | `NA` | Contains a new list for the adapter to load when clicked `KPrefTextContract` | `NA` | `textGetter` | Additional configurations for the text item +`KPrefTimeContract` | `NA` | `use24HourFormat` | Additional configurations for time picker The kpref items are as followed: @@ -41,6 +42,7 @@ Item | Implements | Description `header` | `CoreContract` | Header; just a title that isn't clickable `text` | `CoreContract` `BaseContract` `KPrefTextContract` | Text item; displays the kpref as a String on the right; does not have click implementation by default `plainText` | `CoreContract` `BaseContract` | Plain text item; like `text` but does not deal with any preferences directly, so it doesn't need a getter or setter +`timePicker` | `CoreContract` `BaseContract` `KPrefTextContract`, `KPrefTimeContract` | Extension ot `text` that will open and save a time picker This can be used to display text or deal with preference that are completely handed within the click event (eg a dialog). `subItems` | `CoreContract` `KPrefSubItemsContract` | Sub items; contains a new page for the activity to load when clicked diff --git a/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/KPrefBinder.kt b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/KPrefBinder.kt index 35821fdcb..637af03b4 100644 --- a/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/KPrefBinder.kt +++ b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/KPrefBinder.kt @@ -101,6 +101,14 @@ class KPrefAdapterBuilder(val globalOptions: GlobalOptions) { = list.add(KPrefSeekbar(KPrefSeekbar.KPrefSeekbarBuilder(globalOptions, title, getter, setter) .apply { builder() })) + @KPrefMarker + fun timePicker(@StringRes title: Int, + getter: (() -> Int), + setter: ((value: Int) -> Unit), + builder: KPrefTimePicker.KPrefTimeContract.() -> Unit = {}) + = list.add(KPrefTimePicker(KPrefTimePicker.KPrefTimeBuilder(globalOptions, title, getter, setter) + .apply { builder() })) + @KPrefMarker val list: MutableList = mutableListOf() } \ No newline at end of file diff --git a/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefColorPicker.kt b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefColorPicker.kt index 315d67bf9..19505893d 100644 --- a/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefColorPicker.kt +++ b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefColorPicker.kt @@ -14,7 +14,7 @@ import ca.allanwang.kau.kpref.activity.R * ColorPicker preference * When a color is successfully selected in the dialog, it will be saved as an int */ -open class KPrefColorPicker(val builder: KPrefColorContract) : KPrefItemBase(builder) { +open class KPrefColorPicker(open val builder: KPrefColorContract) : KPrefItemBase(builder) { override fun onPostBindView(viewHolder: ViewHolder, textColor: Int?, accentColor: Int?) { super.onPostBindView(viewHolder, textColor, accentColor) diff --git a/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefPlainText.kt b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefPlainText.kt index b83df69f8..d2e49d86b 100644 --- a/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefPlainText.kt +++ b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefPlainText.kt @@ -12,7 +12,7 @@ import ca.allanwang.kau.kpref.activity.R * and when the preference is completely handled by the click * */ -open class KPrefPlainText(val builder: KPrefPlainTextBuilder) : KPrefItemBase(builder) { +open class KPrefPlainText(open val builder: KPrefPlainTextBuilder) : KPrefItemBase(builder) { override fun defaultOnClick(itemView: View, innerContent: View?): Boolean { //nothing diff --git a/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefSubItems.kt b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefSubItems.kt index 1bf0ffc18..7c2979c50 100644 --- a/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefSubItems.kt +++ b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefSubItems.kt @@ -12,7 +12,7 @@ import ca.allanwang.kau.kpref.activity.R * When clicked, will navigate to a new set of preferences and add the old list to a stack * */ -open class KPrefSubItems(val builder: KPrefSubItemsContract) : KPrefItemCore(builder) { +open class KPrefSubItems(open val builder: KPrefSubItemsContract) : KPrefItemCore(builder) { override fun onClick(itemView: View, innerContent: View?): Boolean { builder.globalOptions.showNextPrefs(builder.titleRes, builder.itemBuilder) diff --git a/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefText.kt b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefText.kt index 45c9a5dd0..29dd0078c 100644 --- a/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefText.kt +++ b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefText.kt @@ -14,7 +14,7 @@ import ca.allanwang.kau.utils.toast * This is still a generic preference * */ -open class KPrefText(val builder: KPrefTextContract) : KPrefItemBase(builder) { +open class KPrefText(open val builder: KPrefTextContract) : KPrefItemBase(builder) { /** * Automatically reload on set diff --git a/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefTimePicker.kt b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefTimePicker.kt new file mode 100644 index 000000000..d4f854b42 --- /dev/null +++ b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefTimePicker.kt @@ -0,0 +1,64 @@ +package ca.allanwang.kau.kpref.activity.items + +import android.app.TimePickerDialog +import android.view.View +import android.widget.TimePicker +import ca.allanwang.kau.kpref.activity.GlobalOptions +import ca.allanwang.kau.kpref.activity.R +import java.util.* + +/** + * Created by Allan Wang on 2017-06-14. + * + * Text preference + * Holds a textview to display data on the right + * This is still a generic preference + * + */ +open class KPrefTimePicker(override val builder: KPrefTimeContract) : KPrefText(builder) { + + interface KPrefTimeContract : KPrefText.KPrefTextContract { + var use24HourFormat: Boolean + } + + /** + * Default implementation of [KPrefTimeContract] + */ + class KPrefTimeBuilder( + globalOptions: GlobalOptions, + titleRes: Int, + getter: () -> Int, + setter: (value: Int) -> Unit + ) : KPrefTimeContract, BaseContract by BaseBuilder(globalOptions, titleRes, getter, setter), TimePickerDialog.OnTimeSetListener { + + override var use24HourFormat: Boolean = false + + override fun onTimeSet(view: TimePicker, hourOfDay: Int, minute: Int) { + setter((hourOfDay to minute).mergeTime) + reloadSelf() + } + + override var textGetter: (Int) -> String? = { + val (hour, min) = it.splitTime + if (use24HourFormat) + String.format(Locale.CANADA, "%d:%02d", hour, min) + else + String.format(Locale.CANADA, "%d:%02d %s", hour % 12, min, if (hour >= 12) "PM" else "AM") + } + + override var onClick: ((itemView: View, innerContent: View?, item: KPrefItemBase) -> Boolean)? = { itemView, _, item -> + val (hour, min) = item.pref.splitTime + TimePickerDialog(itemView.context, this, hour, min, use24HourFormat).show() + true + } + + private val Int.splitTime: Pair + get() = Pair(this / 100, this % 100) + + private val Pair.mergeTime: Int + get() = first * 100 + second + } + + override fun getType(): Int = R.id.kau_item_pref_time_picker + +} \ No newline at end of file diff --git a/sample/src/main/kotlin/ca/allanwang/kau/sample/KPrefSample.kt b/sample/src/main/kotlin/ca/allanwang/kau/sample/KPrefSample.kt index 9adabedeb..0c243e496 100644 --- a/sample/src/main/kotlin/ca/allanwang/kau/sample/KPrefSample.kt +++ b/sample/src/main/kotlin/ca/allanwang/kau/sample/KPrefSample.kt @@ -17,4 +17,7 @@ object KPrefSample : KPref() { var check3: Boolean by kpref("check3", false) var text: String by kpref("text", "empty") var seekbar: Int by kpref("seekbar", 20) + var time12: Int by kpref("time_12", 315) + var time24: Int by kpref("time_24", 2220) + } \ No newline at end of file diff --git a/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt b/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt index 152897098..ca75ebb4e 100644 --- a/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt +++ b/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt @@ -107,8 +107,7 @@ class MainActivity : KPrefActivity() { checkbox(R.string.checkbox_3, { KPrefSample.check3 }, { KPrefSample.check3 = it }) { descRes = R.string.desc_dependent enabler = { KPrefSample.check2 } - onDisabledClick = { - itemView, _, _ -> + onDisabledClick = { itemView, _, _ -> itemView.context.toast("I am still disabled") true } @@ -140,8 +139,7 @@ class MainActivity : KPrefActivity() { text(R.string.text, { KPrefSample.text }, { KPrefSample.text = it }) { descRes = R.string.text_desc - onClick = { - itemView, _, item -> + onClick = { itemView, _, item -> itemView.context.materialDialog { title("Type Text") input("Type here", item.pref, { _, input -> item.pref = input.toString() }) @@ -193,17 +191,25 @@ class MainActivity : KPrefActivity() { textGetter = { string(R.string.kau_lorem_ipsum) } } + timePicker(R.string.time, { KPrefSample.time12 }, { KPrefSample.time12 = it }) { + descRes = R.string.time_desc_12 + use24HourFormat = false + } + + timePicker(R.string.time, { KPrefSample.time24 }, { KPrefSample.time24 = it }) { + descRes = R.string.time_desc_24 + use24HourFormat = true + } + } fun subPrefs(): KPrefAdapterBuilder.() -> Unit = { text(R.string.text, { KPrefSample.text }, { KPrefSample.text = it }) { descRes = R.string.text_desc - onClick = { - itemView, _, item -> + onClick = { itemView, _, item -> itemView.context.materialDialog { title("Type Text") - input("Type here", item.pref, { - _, input -> + input("Type here", item.pref, { _, input -> item.pref = input.toString() reloadSelf() }) @@ -237,21 +243,18 @@ class MainActivity : KPrefActivity() { menuInflater.inflate(R.menu.menu_main, menu) if (searchView == null) searchView = bindSearchView(menu, R.id.action_search) { - textCallback = { - query, searchView -> + textCallback = { query, searchView -> val items = wordBank.filter { it.contains(query) }.sorted().map { SearchItem(it) } searchView.results = items } - searchCallback = { - query, _ -> + searchCallback = { query, _ -> toast("Enter pressed for $query") true } textDebounceInterval = 0 noResultsFound = R.string.kau_no_results_found shouldClearOnClose = false - onItemClick = { - _, _, content, searchView -> + onItemClick = { _, _, content, searchView -> toast(content) searchView.revealClose() } diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml index a887ad366..1e3361ed0 100644 --- a/sample/src/main/res/values/strings.xml +++ b/sample/src/main/res/values/strings.xml @@ -15,6 +15,9 @@ Text Pref Saves the text Seekbar + Time Pref + AM PM version + 24h version Sub Item Pref Press this to view the next subset of preferences your.email@here.com diff --git a/sample/src/main/res/xml/kau_changelog.xml b/sample/src/main/res/xml/kau_changelog.xml index 847666021..7f421f807 100644 --- a/sample/src/main/res/xml/kau_changelog.xml +++ b/sample/src/main/res/xml/kau_changelog.xml @@ -6,9 +6,11 @@ --> - - - + + + + +