diff options
Diffstat (limited to 'kpref-activity/src')
24 files changed, 1139 insertions, 0 deletions
diff --git a/kpref-activity/src/androidTest/java/ca/allanwang/kau/ExampleInstrumentedTest.java b/kpref-activity/src/androidTest/java/ca/allanwang/kau/ExampleInstrumentedTest.java new file mode 100644 index 0000000..7b079b2 --- /dev/null +++ b/kpref-activity/src/androidTest/java/ca/allanwang/kau/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package ca.allanwang.kau; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("ca.allanwang.kau.test", appContext.getPackageName()); + } +} diff --git a/kpref-activity/src/main/AndroidManifest.xml b/kpref-activity/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6c86827 --- /dev/null +++ b/kpref-activity/src/main/AndroidManifest.xml @@ -0,0 +1 @@ +<manifest package="ca.allanwang.kau.kpref.activity" /> diff --git a/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/KPrefActivity.kt b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/KPrefActivity.kt new file mode 100644 index 0000000..be8fad8 --- /dev/null +++ b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/KPrefActivity.kt @@ -0,0 +1,137 @@ +package ca.allanwang.kau.kpref.activity + +import android.os.Bundle +import android.support.annotation.StringRes +import android.support.constraint.ConstraintLayout +import android.support.v7.app.AppCompatActivity +import android.support.v7.widget.RecyclerView +import android.support.v7.widget.Toolbar +import android.view.View +import android.view.animation.Animation +import android.view.animation.AnimationUtils +import android.widget.FrameLayout +import android.widget.ViewAnimator +import ca.allanwang.kau.R +import ca.allanwang.kau.kpref.activity.items.KPrefItemCore +import ca.allanwang.kau.ui.views.RippleCanvas +import ca.allanwang.kau.ui.widgets.TextSlider +import ca.allanwang.kau.utils.bindView +import ca.allanwang.kau.utils.resolveColor +import ca.allanwang.kau.utils.statusBarColor +import ca.allanwang.kau.utils.string +import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter + +abstract class KPrefActivity : AppCompatActivity(), KPrefActivityContract { + + val adapter: FastItemAdapter<KPrefItemCore> + @Suppress("UNCHECKED_CAST") + get() = recycler.adapter as FastItemAdapter<KPrefItemCore> + val recycler: RecyclerView + get() = prefHolder.currentView as RecyclerView + val container: ConstraintLayout by bindView(R.id.kau_container) + val bgCanvas: RippleCanvas by bindView(R.id.kau_ripple) + val toolbarCanvas: RippleCanvas by bindView(R.id.kau_toolbar_ripple) + val toolbar: Toolbar by bindView(R.id.kau_toolbar) + val toolbarTitle: TextSlider by bindView(R.id.kau_toolbar_text) + val prefHolder: ViewAnimator by bindView(R.id.kau_holder) + private lateinit var globalOptions: GlobalOptions + var animate: Boolean = true + set(value) { + field = value + toolbarTitle.animationType = if (value) TextSlider.ANIMATION_SLIDE_HORIZONTAL else TextSlider.ANIMATION_NONE + } + + private val SLIDE_IN_LEFT_ITEMS: Animation by lazy { AnimationUtils.loadAnimation(this, R.anim.kau_slide_in_left) } + private val SLIDE_IN_RIGHT_ITEMS: Animation by lazy { AnimationUtils.loadAnimation(this, R.anim.kau_slide_in_right) } + private val SLIDE_OUT_LEFT_ITEMS: Animation by lazy { AnimationUtils.loadAnimation(this, R.anim.kau_slide_out_left) } + private val SLIDE_OUT_RIGHT_ITEMS: Animation by lazy { AnimationUtils.loadAnimation(this, R.anim.kau_slide_out_right) } + + /** + * Core attribute builder that is consistent throughout all items + * Leave blank to use defaults + */ + abstract fun kPrefCoreAttributes(): CoreAttributeContract.() -> Unit + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + //setup layout + setContentView(R.layout.kau_activity_kpref) + setSupportActionBar(toolbar) + if (supportActionBar != null) + with(supportActionBar!!) { + setDisplayHomeAsUpEnabled(true) + setDisplayShowHomeEnabled(true) + toolbar.setNavigationOnClickListener { onBackPressed() } + setDisplayShowTitleEnabled(false) + } + window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + statusBarColor = 0x30000000 + toolbarCanvas.set(resolveColor(R.attr.colorPrimary)) + bgCanvas.set(resolveColor(android.R.attr.colorBackground)) + prefHolder.animateFirstView = false + //setup prefs + val core = CoreAttributeBuilder() + val builder = kPrefCoreAttributes() + core.builder() + globalOptions = GlobalOptions(core, this) + showNextPrefs(R.string.kau_settings, onCreateKPrefs(savedInstanceState)) + } + + override fun onPostCreate(savedInstanceState: Bundle?) { + super.onPostCreate(savedInstanceState) + } + + override fun showNextPrefs(@StringRes toolbarTitleRes: Int, builder: KPrefAdapterBuilder.() -> Unit) { + val rv = RecyclerView(this).apply { + layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT) + setKPrefAdapter(globalOptions, builder) + } + with(prefHolder) { + inAnimation = if (animate) SLIDE_IN_RIGHT_ITEMS else null + outAnimation = if (animate) SLIDE_OUT_LEFT_ITEMS else null + addView(rv) + showNext() + } + toolbarTitle.setNextText(string(toolbarTitleRes)) + } + + override fun showPrevPrefs() { + val current = prefHolder.currentView + with(prefHolder) { + inAnimation = if (animate) SLIDE_IN_LEFT_ITEMS else null + outAnimation = if (animate) SLIDE_OUT_RIGHT_ITEMS else null + showPrevious() + removeView(current) + adapter.notifyAdapterDataSetChanged() + } + toolbarTitle.setPrevText() + } + + fun reload(vararg index: Int) { + if (index.isEmpty()) adapter.notifyAdapterDataSetChanged() + else index.forEach { adapter.notifyItemChanged(it) } + } + + override fun reloadByTitle(@StringRes vararg title: Int) { + if (title.isEmpty()) return + adapter.adapterItems.forEachIndexed { index, item -> + if (title.any { item.core.titleRes == it }) + adapter.notifyItemChanged(index) + } + } + + abstract fun onCreateKPrefs(savedInstanceState: Bundle?): KPrefAdapterBuilder.() -> Unit + + override fun onBackPressed() { + if (!backPress()) super.onBackPressed() + } + + fun backPress(): Boolean { + if (!toolbarTitle.isRoot) { + showPrevPrefs() + return true + } + return false + } +} + 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 new file mode 100644 index 0000000..6048c1a --- /dev/null +++ b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/KPrefBinder.kt @@ -0,0 +1,128 @@ +package ca.allanwang.kau.kpref.activity + +import android.support.annotation.StringRes +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView +import ca.allanwang.kau.R +import ca.allanwang.kau.kpref.activity.items.* +import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter +import org.jetbrains.anko.doAsync +import org.jetbrains.anko.uiThread + +/** + * Created by Allan Wang on 2017-06-08. + * + * Houses all the components that can be called externally to setup the kpref mainAdapter + */ + +/** + * Base extension that will register the layout manager and mainAdapter with the given items + * Returns FastAdapter + */ +fun RecyclerView.setKPrefAdapter(globalOptions: GlobalOptions, builder: KPrefAdapterBuilder.() -> Unit): FastItemAdapter<KPrefItemCore> { + layoutManager = LinearLayoutManager(context) + val adapter = FastItemAdapter<KPrefItemCore>() + adapter.withOnClickListener { v, _, item, _ -> item.onClick(v, v.findViewById(R.id.kau_pref_inner_content)) } + this.adapter = adapter + doAsync { + val items = KPrefAdapterBuilder(globalOptions) + builder.invoke(items) + uiThread { + adapter.add(items.list) + } + } + return adapter +} + +@DslMarker +annotation class KPrefMarker + +/** + * Contains attributes shared amongst all kpref items + */ +@KPrefMarker +interface CoreAttributeContract { + var textColor: (() -> Int)? + var accentColor: (() -> Int)? +} + +/** + * Implementation of [CoreAttributeContract] + */ +class CoreAttributeBuilder : CoreAttributeContract { + override var textColor: (() -> Int)? = null + override var accentColor: (() -> Int)? = textColor +} + +interface KPrefActivityContract { + fun showNextPrefs(@StringRes toolbarTitleRes: Int, builder: KPrefAdapterBuilder.() -> Unit) + fun showPrevPrefs() + fun reloadByTitle(@StringRes vararg title: Int) +} + + +class GlobalOptions(core: CoreAttributeContract, activity: KPrefActivityContract +) : CoreAttributeContract by core, KPrefActivityContract by activity + + +/** + * Builder for kpref items + * Contains DSLs for every possible item + * The arguments are all the mandatory values plus an optional builder housing all the possible configurations + * The mandatory values are final so they cannot be edited in the builder + */ +@KPrefMarker +class KPrefAdapterBuilder(val globalOptions: GlobalOptions) { + + @KPrefMarker + fun header(@StringRes title: Int) + = list.add(KPrefHeader(KPrefItemCore.CoreBuilder(globalOptions, title))) + + @KPrefMarker + fun checkbox(@StringRes title: Int, + getter: (() -> Boolean), + setter: ((value: Boolean) -> Unit), + builder: KPrefItemBase.BaseContract<Boolean>.() -> Unit = {}) + = list.add(KPrefCheckbox(KPrefItemBase.BaseBuilder(globalOptions, title, getter, setter) + .apply { builder() })) + + @KPrefMarker + fun colorPicker(@StringRes title: Int, + getter: (() -> Int), + setter: ((value: Int) -> Unit), + builder: KPrefColorPicker.KPrefColorContract.() -> Unit = {}) + = list.add(KPrefColorPicker(KPrefColorPicker.KPrefColorBuilder(globalOptions, title, getter, setter) + .apply { builder() })) + + @KPrefMarker + fun <T> text(@StringRes title: Int, + getter: (() -> T), + setter: ((value: T) -> Unit), + builder: KPrefText.KPrefTextContract<T>.() -> Unit = {}) + = list.add(KPrefText<T>(KPrefText.KPrefTextBuilder<T>(globalOptions, title, getter, setter) + .apply { builder() })) + + @KPrefMarker + fun subItems(@StringRes title: Int, + itemBuilder: KPrefAdapterBuilder.() -> Unit, + builder: KPrefSubItems.KPrefSubItemsContract.() -> Unit) + = list.add(KPrefSubItems(KPrefSubItems.KPrefSubItemsBuilder(globalOptions, title, itemBuilder) + .apply { builder() })) + + @KPrefMarker + fun plainText(@StringRes title: Int, + builder: KPrefItemBase.BaseContract<Unit>.() -> Unit = {}) + = list.add(KPrefPlainText(KPrefPlainText.KPrefPlainTextBuilder(globalOptions, title) + .apply { builder() })) + + @KPrefMarker + fun seekbar(@StringRes title: Int, + getter: (() -> Int), + setter: ((value: Int) -> Unit), + builder: KPrefSeekbar.KPrefSeekbarContract.() -> Unit = {}) + = list.add(KPrefSeekbar(KPrefSeekbar.KPrefSeekbarBuilder(globalOptions, title, getter, setter) + .apply { builder() })) + + @KPrefMarker + val list: MutableList<KPrefItemCore> = mutableListOf() +}
\ No newline at end of file diff --git a/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefCheckbox.kt b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefCheckbox.kt new file mode 100644 index 0000000..b7ccc69 --- /dev/null +++ b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefCheckbox.kt @@ -0,0 +1,32 @@ +package ca.allanwang.kau.kpref.activity.items + +import android.view.View +import android.widget.CheckBox +import ca.allanwang.kau.R +import ca.allanwang.kau.utils.tint + +/** + * Created by Allan Wang on 2017-06-07. + * + * Checkbox preference + * When clicked, will toggle the preference and the apply the result to the checkbox + */ +open class KPrefCheckbox(builder: BaseContract<Boolean>) : KPrefItemBase<Boolean>(builder) { + + override fun defaultOnClick(itemView: View, innerContent: View?): Boolean { + pref = !pref + (innerContent as CheckBox).isChecked = pref + return true + } + + override fun onPostBindView(viewHolder: ViewHolder, textColor: Int?, accentColor: Int?) { + super.onPostBindView(viewHolder, textColor, accentColor) + val checkbox = viewHolder.bindInnerView<CheckBox>(R.layout.kau_preference_checkbox) + if (accentColor != null) checkbox.tint(accentColor) + checkbox.isChecked = pref + checkbox.jumpDrawablesToCurrentState() //Cancel the animation + } + + override fun getType(): Int = R.id.kau_item_pref_checkbox + +}
\ 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 new file mode 100644 index 0000000..38008f3 --- /dev/null +++ b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefColorPicker.kt @@ -0,0 +1,71 @@ +package ca.allanwang.kau.kpref.activity.items + +import android.view.View +import ca.allanwang.kau.R +import ca.allanwang.kau.colorpicker.CircleView +import ca.allanwang.kau.colorpicker.ColorBuilder +import ca.allanwang.kau.colorpicker.ColorContract +import ca.allanwang.kau.colorpicker.colorPickerDialog +import ca.allanwang.kau.kpref.activity.GlobalOptions + +/** + * Created by Allan Wang on 2017-06-07. + * + * 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<Int>(builder) { + + override fun onPostBindView(viewHolder: ViewHolder, textColor: Int?, accentColor: Int?) { + super.onPostBindView(viewHolder, textColor, accentColor) + builder.apply { + titleRes = core.titleRes + colorCallback = { + pref = it + } + } + if (builder.showPreview) { + val preview = viewHolder.bindInnerView<CircleView>(R.layout.kau_preference_color_preview) + preview.setBackgroundColor(pref) + preview.withBorder = true + builder.apply { + colorCallback = { + pref = it + if (builder.showPreview) + preview.setBackgroundColor(it) + } + } + } + } + + + override fun defaultOnClick(itemView: View, innerContent: View?): Boolean { + builder.apply { + defaultColor = pref //update color + } + itemView.context.colorPickerDialog(builder).show() + return true + } + + /** + * Extension of the base contract and [ColorContract] along with a showPreview option + */ + interface KPrefColorContract : BaseContract<Int>, ColorContract { + var showPreview: Boolean + } + + /** + * Default implementation of [KPrefColorContract] + */ + class KPrefColorBuilder(globalOptions: GlobalOptions, + override var titleRes: Int, + getter: () -> Int, + setter: (value: Int) -> Unit + ) : KPrefColorContract, BaseContract<Int> by BaseBuilder<Int>(globalOptions, titleRes, getter, setter), + ColorContract by ColorBuilder() { + override var showPreview: Boolean = true + } + + override fun getType(): Int = R.id.kau_item_pref_color_picker + +}
\ No newline at end of file diff --git a/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefHeader.kt b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefHeader.kt new file mode 100644 index 0000000..009caba --- /dev/null +++ b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefHeader.kt @@ -0,0 +1,24 @@ +package ca.allanwang.kau.kpref.activity.items + +import android.view.View +import ca.allanwang.kau.R + +/** + * Created by Allan Wang on 2017-06-07. + * + * Header preference + * This view just holds a title and is not clickable. It is styled using the accent color + */ +open class KPrefHeader(builder: CoreContract) : KPrefItemCore(builder) { + + override fun getLayoutRes(): Int = R.layout.kau_preference_header + + override fun onPostBindView(viewHolder: ViewHolder, textColor: Int?, accentColor: Int?) { + if (accentColor != null) viewHolder.title.setTextColor(accentColor) + } + + override fun onClick(itemView: View, innerContent: View?): Boolean = true + + override fun getType() = R.id.kau_item_pref_header + +}
\ No newline at end of file diff --git a/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefItemBase.kt b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefItemBase.kt new file mode 100644 index 0000000..d7133c1 --- /dev/null +++ b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefItemBase.kt @@ -0,0 +1,84 @@ +package ca.allanwang.kau.kpref.activity.items + +import android.support.annotation.CallSuper +import android.view.View +import ca.allanwang.kau.R +import ca.allanwang.kau.kpref.activity.GlobalOptions +import ca.allanwang.kau.utils.resolveDrawable + +/** + * Created by Allan Wang on 2017-06-05. + * + * Base class for pref setters that include the Shared Preference hooks + */ +abstract class KPrefItemBase<T>(val base: BaseContract<T>) : KPrefItemCore(base) { + + open var pref: T + get() = base.getter.invoke() + set(value) { + base.setter.invoke(value) + } + + var enabled: Boolean = true + + init { + if (base.onClick == null) base.onClick = { + itemView, innerContent, _ -> + defaultOnClick(itemView, innerContent) + } + } + + abstract fun defaultOnClick(itemView: View, innerContent: View?): Boolean + + @CallSuper + override fun onPostBindView(viewHolder: ViewHolder, textColor: Int?, accentColor: Int?) { + enabled = base.enabler.invoke() + with(viewHolder) { + if (!enabled) container?.background = null + container?.alpha = if (enabled) 1.0f else 0.3f + } + } + + override final fun onClick(itemView: View, innerContent: View?): Boolean { + return if (enabled) base.onClick?.invoke(itemView, innerContent, this) ?: false + else base.onDisabledClick?.invoke(itemView, innerContent, this) ?: false + } + + override fun unbindView(holder: ViewHolder) { + super.unbindView(holder) + with(holder) { + container?.isEnabled = true + container?.background = itemView.context.resolveDrawable(android.R.attr.selectableItemBackground) + container?.alpha = 1.0f + } + } + + override final fun getLayoutRes(): Int = R.layout.kau_preference + + /** + * Extension of the core contract + * Since everything that extends the base is an actual preference, there must be a getter and setter + * The rest are optional and will have their defaults + */ + interface BaseContract<T> : CoreContract { + var enabler: () -> Boolean + var onClick: ((itemView: View, innerContent: View?, item: KPrefItemBase<T>) -> Boolean)? + var onDisabledClick: ((itemView: View, innerContent: View?, item: KPrefItemBase<T>) -> Boolean)? + val getter: () -> T + val setter: (value: T) -> Unit + } + + /** + * Default implementation of [BaseContract] + */ + class BaseBuilder<T>(globalOptions: GlobalOptions, + titleRes: Int, + override val getter: () -> T, + override val setter: (value: T) -> Unit + ) : CoreContract by CoreBuilder(globalOptions, titleRes), BaseContract<T> { + override var enabler: () -> Boolean = { true } + override var onClick: ((itemView: View, innerContent: View?, item: KPrefItemBase<T>) -> Boolean)? = null + override var onDisabledClick: ((itemView: View, innerContent: View?, item: KPrefItemBase<T>) -> Boolean)? = null + } + +}
\ No newline at end of file diff --git a/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefItemCore.kt b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefItemCore.kt new file mode 100644 index 0000000..fdda464 --- /dev/null +++ b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefItemCore.kt @@ -0,0 +1,143 @@ +package ca.allanwang.kau.kpref.activity.items + +import android.support.annotation.CallSuper +import android.support.annotation.IdRes +import android.support.annotation.LayoutRes +import android.support.annotation.StringRes +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import ca.allanwang.kau.R +import ca.allanwang.kau.adapters.ThemableIItem +import ca.allanwang.kau.adapters.ThemableIItemDelegate +import ca.allanwang.kau.kpref.activity.GlobalOptions +import ca.allanwang.kau.kpref.activity.KPrefMarker +import ca.allanwang.kau.utils.* +import com.mikepenz.fastadapter.items.AbstractItem +import com.mikepenz.iconics.typeface.IIcon + +/** + * Created by Allan Wang on 2017-06-05. + * + * Core class containing nothing but the view items + */ + +abstract class KPrefItemCore(val core: CoreContract) : AbstractItem<KPrefItemCore, KPrefItemCore.ViewHolder>(), + ThemableIItem by ThemableIItemDelegate() { + + override final fun getViewHolder(v: View) = ViewHolder(v) + + @CallSuper + override fun bindView(viewHolder: ViewHolder, payloads: List<Any>) { + super.bindView(viewHolder, payloads) + with(viewHolder) { + val context = itemView.context + title.text = context.string(core.titleRes) + if (core.descRes > 0) + desc?.visible()?.setText(core.descRes) + else + desc?.gone() + if (core.iicon != null) icon?.visible()?.setIcon(core.iicon, 24) + else icon?.gone() + innerFrame?.removeAllViews() + val textColor = core.globalOptions.textColor?.invoke() + if (textColor != null) { + title.setTextColor(textColor) + desc?.setTextColor(textColor) + } + val accentColor = core.globalOptions.accentColor?.invoke() + if (accentColor != null) { + icon?.drawable?.setTint(accentColor) + } + onPostBindView(this, textColor, accentColor) + } + } + + abstract fun onPostBindView(viewHolder: ViewHolder, textColor: Int?, accentColor: Int?) + + abstract fun onClick(itemView: View, innerContent: View?): Boolean + + override fun unbindView(holder: ViewHolder) { + super.unbindView(holder) + with(holder) { + title.text = null + desc?.text = null + icon?.setImageDrawable(null) + innerFrame?.removeAllViews() + lowerFrame?.removeAllViews() + } + } + + /** + * Core values for all kpref items + */ + @KPrefMarker + interface CoreContract { + val globalOptions: GlobalOptions + @get:StringRes val titleRes: Int + var descRes: Int + @StringRes get + var iicon: IIcon? + + /** + * Attempts to reload current item by identifying it with its [titleRes] + */ + fun reloadSelf() + } + + /** + * Default implementation of [CoreContract] + */ + class CoreBuilder(override val globalOptions: GlobalOptions, + override @param:StringRes val titleRes: Int) : CoreContract { + override var descRes: Int = -1 + override var iicon: IIcon? = null + + override fun reloadSelf() { + globalOptions.reloadByTitle(titleRes) + } + } + + class ViewHolder(v: View) : RecyclerView.ViewHolder(v) { + val title: TextView by bindView(R.id.kau_pref_title) + val container: ViewGroup? by bindOptionalView(R.id.kau_pref_container) + val desc: TextView? by bindOptionalView(R.id.kau_pref_desc) + val icon: ImageView? by bindOptionalView(R.id.kau_pref_icon) + val innerFrame: LinearLayout? by bindOptionalView(R.id.kau_pref_inner_frame) + val lowerFrame: LinearLayout? by bindOptionalView(R.id.kau_pref_lower_frame) + val innerContent: View? + get() = itemView.findViewById(R.id.kau_pref_inner_content) + val lowerContent: View? + get() = itemView.findViewById(R.id.kau_pref_lower_content) + + inline fun <reified T : View> bindInnerView(@LayoutRes id: Int) = bindInnerView(id) { _: T -> } + + inline fun <reified T : View> bindInnerView(@LayoutRes id: Int, onFirstBind: (T) -> Unit): T { + if (innerFrame == null) throw IllegalStateException("Cannot bind inner view when innerFrame does not exist") + if (innerContent !is T) { + innerFrame!!.removeAllViews() + LayoutInflater.from(innerFrame!!.context).inflate(id, innerFrame) + onFirstBind(innerContent as T) + } + return innerContent as T + } + + inline fun <reified T : View> bindLowerView(@LayoutRes id: Int) = bindLowerView(id) { _: T -> } + + inline fun <reified T : View> bindLowerView(@LayoutRes id: Int, onFirstBind: (T) -> Unit): T { + if (lowerFrame == null) throw IllegalStateException("Cannot bind inner view when lowerContent does not exist") + if (lowerContent !is T) { + lowerFrame!!.removeAllViews() + LayoutInflater.from(lowerFrame!!.context).inflate(id, lowerFrame) + onFirstBind(lowerContent as T) + } + return lowerContent as T + } + + operator fun get(@IdRes id: Int): View = itemView.findViewById(id) + } +}
\ No newline at end of file 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 new file mode 100644 index 0000000..dd58cd5 --- /dev/null +++ b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefPlainText.kt @@ -0,0 +1,29 @@ +package ca.allanwang.kau.kpref.activity.items + +import android.view.View +import ca.allanwang.kau.R +import ca.allanwang.kau.kpref.activity.GlobalOptions + +/** + * Created by Allan Wang on 2017-06-14. + * + * Just text with the core options. Extends base preference but has an empty getter and setter + * Useful replacement of [KPrefText] when nothing is displayed on the right side, + * and when the preference is completely handled by the click + * + */ +open class KPrefPlainText(val builder: KPrefPlainTextBuilder) : KPrefItemBase<Unit>(builder) { + + override fun defaultOnClick(itemView: View, innerContent: View?): Boolean { + //nothing + return true + } + + class KPrefPlainTextBuilder( + globalOptions: GlobalOptions, + titleRes: Int + ) : BaseContract<Unit> by BaseBuilder(globalOptions, titleRes, {}, {}) + + override fun getType(): Int = R.id.kau_item_pref_plain_text + +}
\ No newline at end of file diff --git a/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefSeekbar.kt b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefSeekbar.kt new file mode 100644 index 0000000..39b5f12 --- /dev/null +++ b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefSeekbar.kt @@ -0,0 +1,108 @@ +package ca.allanwang.kau.kpref.activity.items + +import android.view.View +import android.widget.SeekBar +import android.widget.TextView +import ca.allanwang.kau.R +import ca.allanwang.kau.kpref.activity.GlobalOptions +import ca.allanwang.kau.kpref.KPrefException +import ca.allanwang.kau.utils.tint + +/** + * Created by Allan Wang on 2017-06-07. + * + * Checkbox preference + * When clicked, will toggle the preference and the apply the result to the checkbox + */ +open class KPrefSeekbar(val builder: KPrefSeekbarContract) : KPrefItemBase<Int>(builder) { + + + override fun defaultOnClick(itemView: View, innerContent: View?): Boolean = false + + override fun onPostBindView(viewHolder: ViewHolder, textColor: Int?, accentColor: Int?) { + super.onPostBindView(viewHolder, textColor, accentColor) + val text = viewHolder.bindInnerView<TextView>(R.layout.kau_preference_seekbar_text) + if (textColor != null) text.setTextColor(textColor) + + val tvc = builder.textViewConfigs + + text.tvc() + val seekbar = viewHolder.bindLowerView<SeekBar>(R.layout.kau_preference_seekbar) { + it.max = builder.range + it.incrementProgressBy(builder.increments) + it.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(sb: SeekBar, progress: Int, fromUser: Boolean) { + text.text = builder.toText(progress.fromProgress) + } + + override fun onStartTrackingTouch(sb: SeekBar) {} + + override fun onStopTrackingTouch(sb: SeekBar) { + val trueProgress = sb.progress.fromProgress + pref = trueProgress + } + }) + } + if (accentColor != null) seekbar.tint(accentColor) + text.text = builder.toText(seekbar.progress.fromProgress) //set initial text in case no change occurs + seekbar.progress = pref.toProgress + } + + /** + * Extension of the base contract + */ + interface KPrefSeekbarContract : BaseContract<Int> { + var min: Int + var max: Int + var increments: Int + var range: Int + /** + * Once a seekbar is let go, calculates what text to show in the text view + */ + var toText: (Int) -> String + var textViewConfigs: TextView.() -> Unit + } + + /** + * Default implementation of [KPrefSeekbarContract] + */ + class KPrefSeekbarBuilder( + globalOptions: GlobalOptions, + titleRes: Int, + getter: () -> Int, + setter: (value: Int) -> Unit + ) : KPrefSeekbarContract, BaseContract<Int> by BaseBuilder(globalOptions, titleRes, getter, setter) { + + override var min: Int = 0 + set(value) { + field = value + range = -1 + } + override var max: Int = 100 + set(value) { + field = value + range = -1 + } + override var increments: Int = 1 + + override var range: Int = max - min + //value doesn't matter; setting will prompt the check + set(value) { + if (max <= min) throw KPrefException("Range min ($min) must be smaller than max ($max)") + field = max - min + } + + override var toText: (Int) -> String = { it.toString() } + + override var textViewConfigs: TextView.() -> Unit = {} + } + + val Int.toProgress: Int + get() = this - builder.min + + val Int.fromProgress: Int + get() = this + builder.min + + override fun getType(): Int = R.id.kau_item_pref_seekbar + +}
\ No newline at end of file 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 new file mode 100644 index 0000000..509394c --- /dev/null +++ b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefSubItems.kt @@ -0,0 +1,43 @@ +package ca.allanwang.kau.kpref.activity.items + +import android.view.View +import ca.allanwang.kau.R +import ca.allanwang.kau.kpref.activity.GlobalOptions +import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder + +/** + * Created by Allan Wang on 2017-06-14. + * + * Sub item preference + * 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) { + + override fun onClick(itemView: View, innerContent: View?): Boolean { + builder.globalOptions.showNextPrefs(builder.titleRes, builder.itemBuilder) + return true + } + + override fun getLayoutRes(): Int = R.layout.kau_preference + + override fun onPostBindView(viewHolder: ViewHolder, textColor: Int?, accentColor: Int?) {} + /** + * Extension of the base contract with an optional text getter + */ + interface KPrefSubItemsContract : CoreContract { + val itemBuilder: KPrefAdapterBuilder.() -> Unit + } + + /** + * Default implementation of [KPrefTextContract] + */ + class KPrefSubItemsBuilder( + globalOptions: GlobalOptions, + titleRes: Int, + override val itemBuilder: KPrefAdapterBuilder.() -> Unit + ) : KPrefSubItemsContract, CoreContract by CoreBuilder(globalOptions, titleRes) + + override fun getType(): Int = R.id.kau_item_pref_sub_item + +}
\ No newline at end of file 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 new file mode 100644 index 0000000..33510c3 --- /dev/null +++ b/kpref-activity/src/main/kotlin/ca/allanwang/kau/kpref/activity/items/KPrefText.kt @@ -0,0 +1,62 @@ +package ca.allanwang.kau.kpref.activity.items + +import android.view.View +import android.widget.TextView +import ca.allanwang.kau.R +import ca.allanwang.kau.kpref.activity.GlobalOptions +import ca.allanwang.kau.utils.toast + +/** + * 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 KPrefText<T>(val builder: KPrefTextContract<T>) : KPrefItemBase<T>(builder) { + + /** + * Automatically reload on set + */ + override var pref: T + get() = base.getter.invoke() + set(value) { + base.setter.invoke(value) + builder.reloadSelf() + } + + override fun defaultOnClick(itemView: View, innerContent: View?): Boolean { + itemView.context.toast("No click function set") + return true + } + + override fun onPostBindView(viewHolder: ViewHolder, textColor: Int?, accentColor: Int?) { + super.onPostBindView(viewHolder, textColor, accentColor) + val textview = viewHolder.bindInnerView<TextView>(R.layout.kau_preference_text) + if (textColor != null) textview.setTextColor(textColor) + textview.text = builder.textGetter.invoke(pref) + } + + /** + * Extension of the base contract with an optional text getter + */ + interface KPrefTextContract<T> : BaseContract<T> { + var textGetter: (T) -> String? + } + + /** + * Default implementation of [KPrefTextContract] + */ + class KPrefTextBuilder<T>( + globalOptions: GlobalOptions, + titleRes: Int, + getter: () -> T, + setter: (value: T) -> Unit + ) : KPrefTextContract<T>, BaseContract<T> by BaseBuilder<T>(globalOptions, titleRes, getter, setter) { + override var textGetter: (T) -> String? = { it?.toString() } + } + + override fun getType(): Int = R.id.kau_item_pref_text + +}
\ No newline at end of file diff --git a/kpref-activity/src/main/res/layout/kau_pref_activity.xml b/kpref-activity/src/main/res/layout/kau_pref_activity.xml new file mode 100644 index 0000000..d068618 --- /dev/null +++ b/kpref-activity/src/main/res/layout/kau_pref_activity.xml @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/kau_container" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ca.allanwang.kau.ui.views.RippleCanvas + android:id="@+id/kau_toolbar_ripple" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toBottomOf="@+id/kau_toolbar" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <android.support.v7.widget.Toolbar + android:id="@id/kau_toolbar" + android:layout_width="0dp" + android:layout_height="?attr/actionBarSize" + android:layout_marginTop="@dimen/kau_status_bar_height" + android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + + <ca.allanwang.kau.widgets.TextSlider + android:id="@+id/kau_toolbar_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + app:animation_type="slide_horizontal" /> + + </android.support.v7.widget.Toolbar> + + <ca.allanwang.kau.ui.views.RippleCanvas + android:id="@+id/kau_ripple" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/kau_toolbar" /> + + <ViewAnimator + android:id="@+id/kau_holder" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/kau_toolbar" /> + +</android.support.constraint.ConstraintLayout> diff --git a/kpref-activity/src/main/res/layout/kau_preference.xml b/kpref-activity/src/main/res/layout/kau_preference.xml new file mode 100644 index 0000000..1a53726 --- /dev/null +++ b/kpref-activity/src/main/res/layout/kau_preference.xml @@ -0,0 +1,108 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!--due to animations, we need a wrapper viewgroup so our changes will stick--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:baselineAligned="false" + android:clipToPadding="false" + android:minHeight="?android:attr/listPreferredItemHeightSmall" + android:orientation="horizontal"> + + <android.support.constraint.ConstraintLayout + android:id="@id/kau_pref_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?android:attr/selectableItemBackground" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:paddingStart="?android:attr/listPreferredItemPaddingStart"> + + <!--As per Android N, icons (24dp) are aligned to the left rather than centered--> + + <ImageView + android:id="@id/kau_pref_icon" + android:layout_width="56dp" + android:layout_height="56dp" + android:layout_marginBottom="4dp" + android:layout_marginTop="4dp" + android:contentDescription="@string/kau_pref_icon" + android:paddingEnd="32dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.5" + tools:layout_editor_absoluteX="0dp" /> + + <TextView + android:id="@id/kau_pref_title" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/kau_padding_normal" + android:ellipsize="marquee" + android:textAppearance="?android:attr/textAppearanceListItem" + android:textColor="?android:attr/textColorPrimary" + app:layout_constraintBottom_toTopOf="@id/kau_pref_desc" + app:layout_constraintEnd_toStartOf="@id/kau_pref_inner_frame" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toEndOf="@id/kau_pref_icon" + app:layout_constraintTop_toTopOf="parent" + tools:layout_editor_absoluteX="-175dp" /> + + <TextView + android:id="@id/kau_pref_desc" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:ellipsize="end" + android:maxLines="10" + android:textAppearance="?android:attr/textAppearanceListItemSecondary" + android:textColor="?android:attr/textColorSecondary" + app:layout_constraintBottom_toTopOf="@id/kau_pref_lower_frame" + app:layout_constraintEnd_toStartOf="@id/kau_pref_inner_frame" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toEndOf="@id/kau_pref_icon" + app:layout_constraintTop_toBottomOf="@id/kau_pref_title" + tools:layout_editor_absoluteX="-175dp" /> + + <LinearLayout + android:id="@id/kau_pref_lower_frame" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/kau_padding_normal" + android:orientation="vertical" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@id/kau_pref_inner_frame" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toEndOf="@id/kau_pref_icon" + app:layout_constraintTop_toBottomOf="@id/kau_pref_desc" + tools:layout_editor_absoluteX="-175dp" /> + + <android.support.constraint.Barrier + android:id="@id/kau_pref_barrier" + android:layout_width="1dp" + android:layout_height="wrap_content" + app:barrierDirection="end" + app:constraint_referenced_ids="kau_pref_title,kau_pref_desc,kau_pref_lower_frame" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <LinearLayout + android:id="@id/kau_pref_inner_frame" + android:layout_width="wrap_content" + android:layout_height="0dp" + android:gravity="center_vertical|end" + android:orientation="horizontal" + android:paddingStart="@dimen/kau_padding_normal" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="1.0" + app:layout_constraintStart_toEndOf="@id/kau_pref_barrier" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.5" + tools:layout_editor_absoluteX="1dp" /> + + </android.support.constraint.ConstraintLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/kpref-activity/src/main/res/layout/kau_preference_checkbox.xml b/kpref-activity/src/main/res/layout/kau_preference_checkbox.xml new file mode 100644 index 0000000..016394f --- /dev/null +++ b/kpref-activity/src/main/res/layout/kau_preference_checkbox.xml @@ -0,0 +1,7 @@ +<CheckBox xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@id/kau_pref_inner_content" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:focusable="false" + android:clickable="false" + android:background="@null" />
\ No newline at end of file diff --git a/kpref-activity/src/main/res/layout/kau_preference_color_preview.xml b/kpref-activity/src/main/res/layout/kau_preference_color_preview.xml new file mode 100644 index 0000000..fbb049b --- /dev/null +++ b/kpref-activity/src/main/res/layout/kau_preference_color_preview.xml @@ -0,0 +1,6 @@ +<ca.allanwang.kau.colorpicker.CircleView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@id/kau_pref_inner_content" + android:layout_width="40dp" + android:layout_height="40dp" + android:focusable="false" + android:clickable="false" />
\ No newline at end of file diff --git a/kpref-activity/src/main/res/layout/kau_preference_header.xml b/kpref-activity/src/main/res/layout/kau_preference_header.xml new file mode 100644 index 0000000..5deece3 --- /dev/null +++ b/kpref-activity/src/main/res/layout/kau_preference_header.xml @@ -0,0 +1,10 @@ +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/kau_pref_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="8dip" + android:paddingEnd="?android:attr/listPreferredItemPaddingRight" + android:paddingStart="?android:attr/listPreferredItemPaddingLeft" + android:paddingTop="16dip" + android:textColor="?android:attr/colorAccent" + android:textSize="14sp" />
\ No newline at end of file diff --git a/kpref-activity/src/main/res/layout/kau_preference_seekbar.xml b/kpref-activity/src/main/res/layout/kau_preference_seekbar.xml new file mode 100644 index 0000000..8da4d5d --- /dev/null +++ b/kpref-activity/src/main/res/layout/kau_preference_seekbar.xml @@ -0,0 +1,8 @@ +<SeekBar xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@id/kau_pref_lower_content" + android:layout_width="match_parent" + android:paddingTop="@dimen/kau_padding_normal" + android:layout_height="wrap_content" + android:focusable="false" + android:clickable="false" + android:background="@null" />
\ No newline at end of file diff --git a/kpref-activity/src/main/res/layout/kau_preference_seekbar_text.xml b/kpref-activity/src/main/res/layout/kau_preference_seekbar_text.xml new file mode 100644 index 0000000..6ba2543 --- /dev/null +++ b/kpref-activity/src/main/res/layout/kau_preference_seekbar_text.xml @@ -0,0 +1,12 @@ +<!--TextView that aligns to the bottom--> + +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@id/kau_pref_inner_content" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom" + android:background="@null" + android:clickable="false" + android:focusable="false" + android:gravity="bottom|end" + android:paddingBottom="@dimen/kau_padding_normal" />
\ No newline at end of file diff --git a/kpref-activity/src/main/res/layout/kau_preference_text.xml b/kpref-activity/src/main/res/layout/kau_preference_text.xml new file mode 100644 index 0000000..a4d901e --- /dev/null +++ b/kpref-activity/src/main/res/layout/kau_preference_text.xml @@ -0,0 +1,7 @@ +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@id/kau_pref_inner_content" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:focusable="false" + android:clickable="false" + android:background="@null" />
\ No newline at end of file diff --git a/kpref-activity/src/main/res/values/ids.xml b/kpref-activity/src/main/res/values/ids.xml new file mode 100644 index 0000000..0972706 --- /dev/null +++ b/kpref-activity/src/main/res/values/ids.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <item name="kau_pref_barrier" type="id" /> + <item name="kau_pref_container" type="id" /> + <item name="kau_pref_desc" type="id" /> + <item name="kau_pref_icon" type="id" /> + <item name="kau_pref_inner_content" type="id" /> + <item name="kau_pref_inner_frame" type="id" /> + <item name="kau_pref_item_checkbox" type="id" /> + <item name="kau_pref_item_color_picker" type="id" /> + <item name="kau_pref_item_header" type="id" /> + <item name="kau_pref_item_plain_text" type="id" /> + <item name="kau_pref_item_seekbar" type="id" /> + <item name="kau_pref_item_sub_item" type="id" /> + <item name="kau_pref_item_text" type="id" /> + <item name="kau_pref_lower_content" type="id" /> + <item name="kau_pref_lower_frame" type="id" /> + <item name="kau_pref_title" type="id" /> +</resources>
\ No newline at end of file diff --git a/kpref-activity/src/main/res/values/strings.xml b/kpref-activity/src/main/res/values/strings.xml new file mode 100644 index 0000000..0402364 --- /dev/null +++ b/kpref-activity/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ +<resources> + <string name="app_name">kpref-activity</string> +</resources> diff --git a/kpref-activity/src/test/java/ca/allanwang/kau/ExampleUnitTest.java b/kpref-activity/src/test/java/ca/allanwang/kau/ExampleUnitTest.java new file mode 100644 index 0000000..a29b447 --- /dev/null +++ b/kpref-activity/src/test/java/ca/allanwang/kau/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package ca.allanwang.kau; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +}
\ No newline at end of file |