From a26f29a059bc1619b9059b84d8c9ba4f5e4966d6 Mon Sep 17 00:00:00 2001 From: Christian Schmitz Date: Sat, 16 Jan 2021 14:59:14 +0100 Subject: [PATCH] feat: provide delegates for AlertDialog and Snackbar and Toast --- README.md | 26 ++- .../astring/appcompat/AStringAlertDialog.kt | 73 ++++++++ .../appcompat/AStringAlertDialogBuilder.kt | 86 ++++++++++ .../AStringAlertDialogBuilderTest.java | 159 +++++++++++++++++ .../appcompat/AStringAlertDialogTest.java | 128 ++++++++++++++ .../AStringAlertDialogBuilderKtTest.kt | 69 ++++++++ .../appcompat/AStringAlertDialogKtTest.kt | 51 ++++++ build.gradle | 2 +- .../tynn/astring/core/AStringAlertDialog.kt | 73 ++++++++ .../astring/core/AStringAlertDialogBuilder.kt | 86 ++++++++++ .../xyz/tynn/astring/core/AStringToast.kt | 51 ++++++ .../core/AStringAlertDialogBuilderTest.java | 159 +++++++++++++++++ .../astring/core/AStringAlertDialogTest.java | 127 ++++++++++++++ .../tynn/astring/core/AStringToastTest.java | 85 ++++++++++ .../xyz/tynn/astring/core/MockkDefinitions.kt | 13 ++ .../core/AStringAlertDialogBuilderKtTest.kt | 69 ++++++++ .../astring/core/AStringAlertDialogKtTest.kt | 51 ++++++ .../tynn/astring/example/common/AStrings.kt | 6 + .../src/main/res/layout/activity_main.xml | 36 ++++ .../common/src/main/res/values/strings.xml | 11 ++ example/common/src/main/res/values/styles.xml | 2 +- .../astring/example/java/MainActivity.java | 83 ++++++++- .../astring/example/kotlin/MainActivity.kt | 53 ++++++ .../tynn/astring/material/AStringSnackbar.kt | 59 +++++++ ...AStringMaterialAlertDialogBuilderTest.java | 160 ++++++++++++++++++ .../astring/material/AStringSnackbarTest.java | 141 +++++++++++++++ .../tynn/astring/material/MockkDefinitions.kt | 27 +++ ...AStringMaterialAlertDialogBuilderKtTest.kt | 70 ++++++++ testing/build.gradle | 2 +- wrapper.gradle | 2 +- 30 files changed, 1954 insertions(+), 6 deletions(-) create mode 100644 appcompat/src/main/kotlin/xyz/tynn/astring/appcompat/AStringAlertDialog.kt create mode 100644 appcompat/src/main/kotlin/xyz/tynn/astring/appcompat/AStringAlertDialogBuilder.kt create mode 100644 appcompat/src/test/java/xyz/tynn/astring/appcompat/AStringAlertDialogBuilderTest.java create mode 100644 appcompat/src/test/java/xyz/tynn/astring/appcompat/AStringAlertDialogTest.java create mode 100644 appcompat/src/test/kotlin/xyz/tynn/astring/appcompat/AStringAlertDialogBuilderKtTest.kt create mode 100644 appcompat/src/test/kotlin/xyz/tynn/astring/appcompat/AStringAlertDialogKtTest.kt create mode 100644 core/src/main/kotlin/xyz/tynn/astring/core/AStringAlertDialog.kt create mode 100644 core/src/main/kotlin/xyz/tynn/astring/core/AStringAlertDialogBuilder.kt create mode 100644 core/src/main/kotlin/xyz/tynn/astring/core/AStringToast.kt create mode 100644 core/src/test/java/xyz/tynn/astring/core/AStringAlertDialogBuilderTest.java create mode 100644 core/src/test/java/xyz/tynn/astring/core/AStringAlertDialogTest.java create mode 100644 core/src/test/java/xyz/tynn/astring/core/AStringToastTest.java create mode 100644 core/src/test/java/xyz/tynn/astring/core/MockkDefinitions.kt create mode 100644 core/src/test/kotlin/xyz/tynn/astring/core/AStringAlertDialogBuilderKtTest.kt create mode 100644 core/src/test/kotlin/xyz/tynn/astring/core/AStringAlertDialogKtTest.kt create mode 100644 material/src/main/kotlin/xyz/tynn/astring/material/AStringSnackbar.kt create mode 100644 material/src/test/java/xyz/tynn/astring/material/AStringMaterialAlertDialogBuilderTest.java create mode 100644 material/src/test/java/xyz/tynn/astring/material/AStringSnackbarTest.java create mode 100644 material/src/test/java/xyz/tynn/astring/material/MockkDefinitions.kt create mode 100644 material/src/test/kotlin/xyz/tynn/astring/material/AStringMaterialAlertDialogBuilderKtTest.kt diff --git a/README.md b/README.md index 3252b50..ce07cf8 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,18 @@ public interface AString { The library is implemented with _Kotlin_ for _Java_. The _Kotlin Standard Library_ is not required to use _AString_. +## Installation + + repositories { + jcenter() + } + + dependencies { + implementation "xyz.tynn.astring:core:$aStringVersion" + implementation "xyz.tynn.astring:appcompat:$aStringVersion" + implementation "xyz.tynn.astring:material:$aStringVersion" + } + ## Usage @@ -54,7 +66,7 @@ There are several _core_ implementations of `AString` for: * quantity string resources delegation * formatted quantity string resources delegation -### Supported `View` types +### Supported _Android_ types There are several (_Kotlin_) extension overloads for methods taking a `CharSequence` as an argument. @@ -72,18 +84,30 @@ Views provided by the _Android_ framework and _AndroidX_ core: * `ToggleButton` * `TextSwitcher` + * `Toast` + + * `AlertDialog` + * `AlertDialog.Builder` + #### AppCompat module Views provided by _AndroidX_ appcompat: * `Toobar` + * `AlertDialog` + * `AlertDialog.Builder` + #### Material module Views provided by the _Material_ components by _Google_: * `TextInputLayout` + * `Snackbar` + + * `MaterialAlertDialogBuilder` + ## License diff --git a/appcompat/src/main/kotlin/xyz/tynn/astring/appcompat/AStringAlertDialog.kt b/appcompat/src/main/kotlin/xyz/tynn/astring/appcompat/AStringAlertDialog.kt new file mode 100644 index 0000000..5b5f27b --- /dev/null +++ b/appcompat/src/main/kotlin/xyz/tynn/astring/appcompat/AStringAlertDialog.kt @@ -0,0 +1,73 @@ +// Copyright 2021 Christian Schmitz +// SPDX-License-Identifier: Apache-2.0 + +@file:JvmName("AStringAlertDialog") + +package xyz.tynn.astring.appcompat + +import android.content.DialogInterface.* +import android.os.Message +import androidx.annotation.IntDef +import androidx.appcompat.app.AlertDialog +import xyz.tynn.astring.AString +import kotlin.annotation.AnnotationRetention.SOURCE + +@Retention(SOURCE) +@IntDef(BUTTON_POSITIVE, BUTTON_NEGATIVE, BUTTON_NEUTRAL) +private annotation class DialogInterfaceButton + +/** + * Sets a listener to be invoked when the button is pressed + * + * This method has no effect if called after [AlertDialog.show] + * + * @see AlertDialog.setButton + */ +fun AlertDialog.setButton( + @DialogInterfaceButton whichButton: Int, + text: AString, + listener: OnClickListener?, +) = setButton( + whichButton, + text(context), + listener, +) + +/** + * Sets a message to be sent when a button is pressed + * + * This method has no effect if called after [AlertDialog.show] + * + * @see AlertDialog.setButton + */ +fun AlertDialog.setButton( + @DialogInterfaceButton whichButton: Int, + text: AString, + msg: Message?, +) = setButton( + whichButton, + text(context), + msg, +) + +/** + * Sets the message to display + * + * @see AlertDialog.setMessage + */ +fun AlertDialog.setMessage( + message: AString, +) = setMessage( + message(context), +) + +/** + * Sets the title to display + * + * @see AlertDialog.setTitle + */ +fun AlertDialog.setTitle( + title: AString, +) = setTitle( + title(context), +) diff --git a/appcompat/src/main/kotlin/xyz/tynn/astring/appcompat/AStringAlertDialogBuilder.kt b/appcompat/src/main/kotlin/xyz/tynn/astring/appcompat/AStringAlertDialogBuilder.kt new file mode 100644 index 0000000..752b236 --- /dev/null +++ b/appcompat/src/main/kotlin/xyz/tynn/astring/appcompat/AStringAlertDialogBuilder.kt @@ -0,0 +1,86 @@ +// Copyright 2021 Christian Schmitz +// SPDX-License-Identifier: Apache-2.0 + +@file:JvmName("AStringAlertDialogBuilder") + +package xyz.tynn.astring.appcompat + +import android.content.DialogInterface.OnClickListener +import androidx.appcompat.app.AlertDialog.Builder +import xyz.tynn.astring.AString + +/** + * Sets a listener to be invoked when the negative button is pressed + * + * @see Builder.setNegativeButton + */ +fun B.setNegativeButton( + text: AString, + listener: OnClickListener?, +): B { + setNegativeButton( + text(context), + listener, + ) + return this +} + +/** + * Sets a listener to be invoked when the neutral button is pressed + * + * @see Builder.setNeutralButton + */ +fun B.setNeutralButton( + text: AString, + listener: OnClickListener?, +): B { + setNeutralButton( + text(context), + listener, + ) + return this +} + +/** + * Sets a listener to be invoked when the positive button is pressed + * + * @see Builder.setPositiveButton + */ +fun B.setPositiveButton( + text: AString, + listener: OnClickListener?, +): B { + setPositiveButton( + text(context), + listener, + ) + return this +} + +/** + * Sets the message + * + * @see Builder.setMessage + */ +fun B.setMessage( + message: AString, +): B { + setMessage( + message(context), + ) + return this +} + +/** + * Sets the title + * + * @see Builder.setTitle + */ +fun B.setTitle( + title: AString, +): B { + setTitle( + title(context), + ) + return this +} diff --git a/appcompat/src/test/java/xyz/tynn/astring/appcompat/AStringAlertDialogBuilderTest.java b/appcompat/src/test/java/xyz/tynn/astring/appcompat/AStringAlertDialogBuilderTest.java new file mode 100644 index 0000000..f8f0ff1 --- /dev/null +++ b/appcompat/src/test/java/xyz/tynn/astring/appcompat/AStringAlertDialogBuilderTest.java @@ -0,0 +1,159 @@ +// Copyright 2021 Christian Schmitz +// SPDX-License-Identifier: Apache-2.0 + +package xyz.tynn.astring.appcompat; + +import android.content.DialogInterface.OnClickListener; + +import androidx.appcompat.app.AlertDialog; + +import org.junit.Before; +import org.junit.Test; + +import io.mockk.impl.annotations.MockK; +import xyz.tynn.astring.AString; + +import static org.junit.Assert.assertSame; +import static xyz.tynn.astring.testing.mockk.MockKt.init; +import static xyz.tynn.astring.testing.mockk.MockKt.verify; + +public class AStringAlertDialogBuilderTest { + + @MockK + AString aString; + + @MockK + AlertDialog.Builder builder; + + @MockK + OnClickListener listener; + + @Before + public void setup() { + init(this, true); + } + + @Test + public void setNegativeButton_should_delegate_to_builder() { + assertSame(builder, + AStringAlertDialogBuilder.setNegativeButton(builder, aString, listener)); + + verify(() -> builder.setNegativeButton(aString.invoke(builder.getContext()), listener)); + } + + @Test + public void setNegativeButton_with_null_listener_should_delegate_to_builder() { + assertSame(builder, + AStringAlertDialogBuilder.setNegativeButton(builder, aString, null)); + + verify(() -> builder.setNegativeButton(aString.invoke(builder.getContext()), null)); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setNegativeButton_should_throw_on_null_builder() { + AStringAlertDialogBuilder.setNegativeButton(null, aString, listener); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setNegativeButton_should_throw_on_null_string() { + AStringAlertDialogBuilder.setNegativeButton(builder, null, listener); + } + + @Test + public void setNeutralButton_should_delegate_to_builder() { + assertSame(builder, + AStringAlertDialogBuilder.setNeutralButton(builder, aString, listener)); + + verify(() -> builder.setNeutralButton(aString.invoke(builder.getContext()), listener)); + } + + @Test + public void setNeutralButton_with_null_listener_should_delegate_to_builder() { + assertSame(builder, + AStringAlertDialogBuilder.setNeutralButton(builder, aString, null)); + + verify(() -> builder.setNeutralButton(aString.invoke(builder.getContext()), null)); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setNeutralButton_should_throw_on_null_builder() { + AStringAlertDialogBuilder.setNeutralButton(null, aString, listener); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setNeutralButton_should_throw_on_null_string() { + AStringAlertDialogBuilder.setNeutralButton(builder, null, listener); + } + + @Test + public void setPositiveButton_should_delegate_to_builder() { + assertSame(builder, + AStringAlertDialogBuilder.setPositiveButton(builder, aString, listener)); + + verify(() -> builder.setPositiveButton(aString.invoke(builder.getContext()), listener)); + } + + @Test + public void setPositiveButton_with_null_listener_should_delegate_to_builder() { + assertSame(builder, + AStringAlertDialogBuilder.setPositiveButton(builder, aString, null)); + + verify(() -> builder.setPositiveButton(aString.invoke(builder.getContext()), null)); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setPositiveButton_should_throw_on_null_builder() { + AStringAlertDialogBuilder.setPositiveButton(null, aString, listener); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setPositiveButton_should_throw_on_null_string() { + AStringAlertDialogBuilder.setPositiveButton(builder, null, listener); + } + + @Test + public void setMessage_should_delegate_to_builder() { + assertSame(builder, + AStringAlertDialogBuilder.setMessage(builder, aString)); + + verify(() -> builder.setMessage(aString.invoke(builder.getContext()))); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setMessage_should_throw_on_null_builder() { + AStringAlertDialogBuilder.setMessage(null, aString); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setMessage_should_throw_on_null_string() { + AStringAlertDialogBuilder.setMessage(builder, null); + } + + @Test + public void setTitle_should_delegate_to_builder() { + assertSame(builder, + AStringAlertDialogBuilder.setTitle(builder, aString)); + + verify(() -> builder.setTitle(aString.invoke(builder.getContext()))); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setTitle_should_throw_on_null_builder() { + AStringAlertDialogBuilder.setTitle(null, aString); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setTitle_should_throw_on_null_string() { + AStringAlertDialogBuilder.setTitle(builder, null); + } +} diff --git a/appcompat/src/test/java/xyz/tynn/astring/appcompat/AStringAlertDialogTest.java b/appcompat/src/test/java/xyz/tynn/astring/appcompat/AStringAlertDialogTest.java new file mode 100644 index 0000000..b49f888 --- /dev/null +++ b/appcompat/src/test/java/xyz/tynn/astring/appcompat/AStringAlertDialogTest.java @@ -0,0 +1,128 @@ +// Copyright 2021 Christian Schmitz +// SPDX-License-Identifier: Apache-2.0 + +package xyz.tynn.astring.appcompat; + +import android.content.DialogInterface.OnClickListener; +import android.os.Message; + +import androidx.appcompat.app.AlertDialog; + +import org.junit.Before; +import org.junit.Test; + +import io.mockk.impl.annotations.MockK; +import xyz.tynn.astring.AString; + +import static android.content.DialogInterface.BUTTON_NEUTRAL; +import static xyz.tynn.astring.testing.mockk.MockKt.init; +import static xyz.tynn.astring.testing.mockk.MockKt.verify; + +public class AStringAlertDialogTest { + + @MockK + AString aString; + + @MockK + AlertDialog dialog; + + @MockK + OnClickListener listener; + @MockK + Message message; + + @Before + public void setup() { + init(this, true); + } + + @Test + public void setButton_with_listener_should_delegate_to_dialog() { + AStringAlertDialog.setButton(dialog, BUTTON_NEUTRAL, aString, listener); + + verify(() -> dialog.setButton(BUTTON_NEUTRAL, aString.invoke(dialog.getContext()), listener)); + } + + @Test + public void setButton_with_null_listener_should_delegate_to_dialog() { + AStringAlertDialog.setButton(dialog, BUTTON_NEUTRAL, aString, (OnClickListener) null); + + verify(() -> dialog.setButton(BUTTON_NEUTRAL, aString.invoke(dialog.getContext()), (OnClickListener) null)); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setButton_with_listener_should_throw_on_null_dialog() { + AStringAlertDialog.setButton(null, BUTTON_NEUTRAL, aString, listener); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setButton_with_listener_should_throw_on_null_string() { + AStringAlertDialog.setButton(dialog, BUTTON_NEUTRAL, null, listener); + } + + @Test + public void setButton_with_message_should_delegate_to_dialog() { + AStringAlertDialog.setButton(dialog, BUTTON_NEUTRAL, aString, message); + + verify(() -> dialog.setButton(BUTTON_NEUTRAL, aString.invoke(dialog.getContext()), message)); + } + + @Test + public void setButton_with_null_message_should_delegate_to_dialog() { + AStringAlertDialog.setButton(dialog, BUTTON_NEUTRAL, aString, (Message) null); + + verify(() -> dialog.setButton(BUTTON_NEUTRAL, aString.invoke(dialog.getContext()), (Message) null)); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setButton_with_message_should_throw_on_null_dialog() { + AStringAlertDialog.setButton(null, BUTTON_NEUTRAL, aString, message); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setButton_with_message_should_throw_on_null_string() { + AStringAlertDialog.setButton(dialog, BUTTON_NEUTRAL, null, message); + } + + @Test + public void setMessage_should_delegate_to_dialog() { + AStringAlertDialog.setMessage(dialog, aString); + + verify(() -> dialog.setMessage(aString.invoke(dialog.getContext()))); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setMessage_should_throw_on_null_dialog() { + AStringAlertDialog.setMessage(null, aString); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setMessage_should_throw_on_null_string() { + AStringAlertDialog.setMessage(dialog, null); + } + + @Test + public void setTitle_should_delegate_to_dialog() { + AStringAlertDialog.setTitle(dialog, aString); + + verify(() -> dialog.setTitle(aString.invoke(dialog.getContext()))); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setTitle_should_throw_on_null_dialog() { + AStringAlertDialog.setTitle(null, aString); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setTitle_should_throw_on_null_string() { + AStringAlertDialog.setTitle(dialog, null); + } +} diff --git a/appcompat/src/test/kotlin/xyz/tynn/astring/appcompat/AStringAlertDialogBuilderKtTest.kt b/appcompat/src/test/kotlin/xyz/tynn/astring/appcompat/AStringAlertDialogBuilderKtTest.kt new file mode 100644 index 0000000..e2666ca --- /dev/null +++ b/appcompat/src/test/kotlin/xyz/tynn/astring/appcompat/AStringAlertDialogBuilderKtTest.kt @@ -0,0 +1,69 @@ +// Copyright 2021 Christian Schmitz +// SPDX-License-Identifier: Apache-2.0 + +package xyz.tynn.astring.appcompat + +import android.content.DialogInterface.OnClickListener +import androidx.appcompat.app.AlertDialog +import io.mockk.mockk +import io.mockk.verify +import xyz.tynn.astring.AString +import kotlin.test.Test +import kotlin.test.assertEquals + +internal class AStringAlertDialogBuilderKtTest { + + val aString = mockk(relaxed = true) + val builder = mockk(relaxed = true) + val listener = mockk() + + @Test + fun `setNegativeButton should delegate to builder`() { + assertEquals( + builder, + builder.setNegativeButton(aString, listener), + ) + + verify { builder.setNegativeButton(aString(builder.context), listener) } + } + + @Test + fun `setNeutralButton should delegate to builder`() { + assertEquals( + builder, + builder.setNeutralButton(aString, listener), + ) + + verify { builder.setNeutralButton(aString(builder.context), listener) } + } + + @Test + fun `setPositiveButton should delegate to builder`() { + assertEquals( + builder, + builder.setPositiveButton(aString, listener), + ) + + verify { builder.setPositiveButton(aString(builder.context), listener) } + } + + @Test + fun `setMessage should delegate to builder`() { + assertEquals( + builder, + builder.setMessage(aString), + ) + + verify { builder.setMessage(aString(builder.context)) } + } + + @Test + fun `setTitle should delegate to builder`() { + assertEquals( + builder, + builder.setTitle(aString), + ) + + verify { builder.setTitle(aString(builder.context)) } + } +} diff --git a/appcompat/src/test/kotlin/xyz/tynn/astring/appcompat/AStringAlertDialogKtTest.kt b/appcompat/src/test/kotlin/xyz/tynn/astring/appcompat/AStringAlertDialogKtTest.kt new file mode 100644 index 0000000..97a2857 --- /dev/null +++ b/appcompat/src/test/kotlin/xyz/tynn/astring/appcompat/AStringAlertDialogKtTest.kt @@ -0,0 +1,51 @@ +// Copyright 2021 Christian Schmitz +// SPDX-License-Identifier: Apache-2.0 + +package xyz.tynn.astring.appcompat + +import android.content.DialogInterface.BUTTON_NEUTRAL +import android.content.DialogInterface.OnClickListener +import android.os.Message +import androidx.appcompat.app.AlertDialog +import io.mockk.mockk +import io.mockk.verify +import xyz.tynn.astring.AString +import kotlin.test.Test + +internal class AStringAlertDialogKtTest { + + val aString = mockk(relaxed = true) + val dialog = mockk(relaxed = true) + + @Test + fun `setNegativeButton with listener should delegate to dialog`() { + val listener = mockk() + + dialog.setButton(BUTTON_NEUTRAL, aString, listener) + + verify { dialog.setButton(BUTTON_NEUTRAL, aString(dialog.context), listener) } + } + + @Test + fun `setNegativeButton with message should delegate to dialog`() { + val message = mockk() + + dialog.setButton(BUTTON_NEUTRAL, aString, message) + + verify { dialog.setButton(BUTTON_NEUTRAL, aString(dialog.context), message) } + } + + @Test + fun `setMessage should delegate to dialog`() { + dialog.setMessage(aString) + + verify { dialog.setMessage(aString(dialog.context)) } + } + + @Test + fun `setTitle should delegate to dialog`() { + dialog.setTitle(aString) + + verify { dialog.setTitle(aString(dialog.context)) } + } +} diff --git a/build.gradle b/build.gradle index 02f2ada..c09e043 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id 'com.android.library' version '4.1.1' apply false - id 'org.jetbrains.kotlin.android' version '1.4.21' apply false + id 'org.jetbrains.kotlin.android' version '1.4.21-2' apply false id 'xyz.tynn.android.maven' version '0.1.0' apply false id 'com.github.ben-manes.versions' version '0.36.0' id 'xyz.tynn.idea.fix' version '0.1.2' diff --git a/core/src/main/kotlin/xyz/tynn/astring/core/AStringAlertDialog.kt b/core/src/main/kotlin/xyz/tynn/astring/core/AStringAlertDialog.kt new file mode 100644 index 0000000..2dadfcf --- /dev/null +++ b/core/src/main/kotlin/xyz/tynn/astring/core/AStringAlertDialog.kt @@ -0,0 +1,73 @@ +// Copyright 2021 Christian Schmitz +// SPDX-License-Identifier: Apache-2.0 + +@file:JvmName("AStringAlertDialog") + +package xyz.tynn.astring.core + +import android.app.AlertDialog +import android.content.DialogInterface.* +import android.os.Message +import androidx.annotation.IntDef +import xyz.tynn.astring.AString +import kotlin.annotation.AnnotationRetention.SOURCE + +@Retention(SOURCE) +@IntDef(BUTTON_POSITIVE, BUTTON_NEGATIVE, BUTTON_NEUTRAL) +private annotation class DialogInterfaceButton + +/** + * Sets a listener to be invoked when the button is pressed + * + * This method has no effect if called after [AlertDialog.show] + * + * @see AlertDialog.setButton + */ +fun AlertDialog.setButton( + @DialogInterfaceButton whichButton: Int, + text: AString, + listener: OnClickListener?, +) = setButton( + whichButton, + text(context), + listener, +) + +/** + * Sets a message to be sent when a button is pressed + * + * This method has no effect if called after [AlertDialog.show] + * + * @see AlertDialog.setButton + */ +fun AlertDialog.setButton( + @DialogInterfaceButton whichButton: Int, + text: AString, + msg: Message?, +) = setButton( + whichButton, + text(context), + msg, +) + +/** + * Sets the message to display + * + * @see AlertDialog.setMessage + */ +fun AlertDialog.setMessage( + message: AString, +) = setMessage( + message(context), +) + +/** + * Sets the title to display + * + * @see AlertDialog.setTitle + */ +fun AlertDialog.setTitle( + title: AString, +) = setTitle( + title(context), +) diff --git a/core/src/main/kotlin/xyz/tynn/astring/core/AStringAlertDialogBuilder.kt b/core/src/main/kotlin/xyz/tynn/astring/core/AStringAlertDialogBuilder.kt new file mode 100644 index 0000000..3b56da9 --- /dev/null +++ b/core/src/main/kotlin/xyz/tynn/astring/core/AStringAlertDialogBuilder.kt @@ -0,0 +1,86 @@ +// Copyright 2021 Christian Schmitz +// SPDX-License-Identifier: Apache-2.0 + +@file:JvmName("AStringAlertDialogBuilder") + +package xyz.tynn.astring.core + +import android.app.AlertDialog.Builder +import android.content.DialogInterface.OnClickListener +import xyz.tynn.astring.AString + +/** + * Sets a listener to be invoked when the negative button is pressed + * + * @see Builder.setNegativeButton + */ +fun B.setNegativeButton( + text: AString, + listener: OnClickListener?, +): B { + setNegativeButton( + text(context), + listener, + ) + return this +} + +/** + * Sets a listener to be invoked when the neutral button is pressed + * + * @see Builder.setNeutralButton + */ +fun B.setNeutralButton( + text: AString, + listener: OnClickListener?, +): B { + setNeutralButton( + text(context), + listener, + ) + return this +} + +/** + * Sets a listener to be invoked when the positive button is pressed + * + * @see Builder.setPositiveButton + */ +fun B.setPositiveButton( + text: AString, + listener: OnClickListener?, +): B { + setPositiveButton( + text(context), + listener, + ) + return this +} + +/** + * Sets the message + * + * @see Builder.setMessage + */ +fun B.setMessage( + message: AString, +): B { + setMessage( + message(context), + ) + return this +} + +/** + * Sets the title + * + * @see Builder.setTitle + */ +fun B.setTitle( + title: AString, +): B { + setTitle( + title(context), + ) + return this +} diff --git a/core/src/main/kotlin/xyz/tynn/astring/core/AStringToast.kt b/core/src/main/kotlin/xyz/tynn/astring/core/AStringToast.kt new file mode 100644 index 0000000..257502b --- /dev/null +++ b/core/src/main/kotlin/xyz/tynn/astring/core/AStringToast.kt @@ -0,0 +1,51 @@ +// Copyright 2021 Christian Schmitz +// SPDX-License-Identifier: Apache-2.0 + +@file:JvmName("AStringToast") + +package xyz.tynn.astring.core + +import android.annotation.SuppressLint +import android.content.Context +import android.widget.Toast +import android.widget.Toast.* +import androidx.annotation.IntDef +import xyz.tynn.astring.AString +import java.util.Objects.requireNonNull +import kotlin.annotation.AnnotationRetention.SOURCE + +@Retention(SOURCE) +@IntDef(LENGTH_LONG, LENGTH_SHORT) +private annotation class ToastDuration + +/** + * Makes a standard toast that contains the text + * + * @see Toast.makeText + */ +@SuppressLint("ShowToast") +@JvmName("makeText") +fun makeToast( + context: Context, + text: AString, + @ToastDuration duration: Int, +): Toast = makeText( + context, + text(context), + duration, +) + +/** + * Updates the text in this [Toast] + * + * The [context] is required due to the private [Context] reference within the [Toast] + * + * @see Toast.setText + */ +fun Toast.setText( + context: Context, + text: AString, +) = setText( + text(requireNonNull(context)), +) + diff --git a/core/src/test/java/xyz/tynn/astring/core/AStringAlertDialogBuilderTest.java b/core/src/test/java/xyz/tynn/astring/core/AStringAlertDialogBuilderTest.java new file mode 100644 index 0000000..795a8bb --- /dev/null +++ b/core/src/test/java/xyz/tynn/astring/core/AStringAlertDialogBuilderTest.java @@ -0,0 +1,159 @@ +// Copyright 2021 Christian Schmitz +// SPDX-License-Identifier: Apache-2.0 + +package xyz.tynn.astring.core; + +import android.app.AlertDialog; +import android.content.DialogInterface.OnClickListener; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import io.mockk.impl.annotations.MockK; +import xyz.tynn.astring.AString; + +import static org.junit.Assert.assertSame; +import static xyz.tynn.astring.testing.mockk.MockKt.init; +import static xyz.tynn.astring.testing.mockk.MockKt.verify; + +public class AStringAlertDialogBuilderTest { + + @MockK + AString aString; + + @MockK + AlertDialog.Builder builder; + + @MockK + OnClickListener listener; + + @Before + public void setup() { + init(this, true); + } + + @Test + public void setNegativeButton_should_delegate_to_builder() { + assertSame(builder, + AStringAlertDialogBuilder.setNegativeButton(builder, aString, listener)); + + verify(() -> builder.setNegativeButton(aString.invoke(builder.getContext()), listener)); + } + + @Test + public void setNegativeButton_with_null_listener_should_delegate_to_builder() { + assertSame(builder, + AStringAlertDialogBuilder.setNegativeButton(builder, aString, null)); + + verify(() -> builder.setNegativeButton(aString.invoke(builder.getContext()), null)); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setNegativeButton_should_throw_on_null_builder() { + AStringAlertDialogBuilder.setNegativeButton(null, aString, listener); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setNegativeButton_should_throw_on_null_string() { + AStringAlertDialogBuilder.setNegativeButton(builder, null, listener); + } + + @Test + public void setNeutralButton_should_delegate_to_builder() { + assertSame(builder, + AStringAlertDialogBuilder.setNeutralButton(builder, aString, listener)); + + verify(() -> builder.setNeutralButton(aString.invoke(builder.getContext()), listener)); + } + + @Test + public void setNeutralButton_with_null_listener_should_delegate_to_builder() { + assertSame(builder, + AStringAlertDialogBuilder.setNeutralButton(builder, aString, null)); + + verify(() -> builder.setNeutralButton(aString.invoke(builder.getContext()), null)); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setNeutralButton_should_throw_on_null_builder() { + AStringAlertDialogBuilder.setNeutralButton(null, aString, listener); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setNeutralButton_should_throw_on_null_string() { + AStringAlertDialogBuilder.setNeutralButton(builder, null, listener); + } + + @Test + public void setPositiveButton_should_delegate_to_builder() { + assertSame(builder, + AStringAlertDialogBuilder.setPositiveButton(builder, aString, listener)); + + verify(() -> builder.setPositiveButton(aString.invoke(builder.getContext()), listener)); + } + + @Test + public void setPositiveButton_with_null_listener_should_delegate_to_builder() { + assertSame(builder, + AStringAlertDialogBuilder.setPositiveButton(builder, aString, null)); + + verify(() -> builder.setPositiveButton(aString.invoke(builder.getContext()), null)); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setPositiveButton_should_throw_on_null_builder() { + AStringAlertDialogBuilder.setPositiveButton(null, aString, listener); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setPositiveButton_should_throw_on_null_string() { + AStringAlertDialogBuilder.setPositiveButton(builder, null, listener); + } + + @Test + public void setMessage_should_delegate_to_builder() { + assertSame(builder, + AStringAlertDialogBuilder.setMessage(builder, aString)); + + verify(() -> builder.setMessage(aString.invoke(builder.getContext()))); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setMessage_should_throw_on_null_builder() { + AStringAlertDialogBuilder.setMessage(null, aString); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setMessage_should_throw_on_null_string() { + AStringAlertDialogBuilder.setMessage(builder, null); + } + + @Test + public void setTitle_should_delegate_to_builder() { + assertSame(builder, + AStringAlertDialogBuilder.setTitle(builder, aString)); + + verify(() -> builder.setTitle(aString.invoke(builder.getContext()))); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setTitle_should_throw_on_null_builder() { + AStringAlertDialogBuilder.setTitle(null, aString); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setTitle_should_throw_on_null_string() { + AStringAlertDialogBuilder.setTitle(builder, null); + } +} diff --git a/core/src/test/java/xyz/tynn/astring/core/AStringAlertDialogTest.java b/core/src/test/java/xyz/tynn/astring/core/AStringAlertDialogTest.java new file mode 100644 index 0000000..ad4d577 --- /dev/null +++ b/core/src/test/java/xyz/tynn/astring/core/AStringAlertDialogTest.java @@ -0,0 +1,127 @@ +// Copyright 2021 Christian Schmitz +// SPDX-License-Identifier: Apache-2.0 + +package xyz.tynn.astring.core; + +import android.app.AlertDialog; +import android.content.DialogInterface.OnClickListener; +import android.os.Message; + +import org.junit.Before; +import org.junit.Test; + +import io.mockk.impl.annotations.MockK; +import xyz.tynn.astring.AString; + +import static android.content.DialogInterface.BUTTON_NEUTRAL; +import static xyz.tynn.astring.testing.mockk.MockKt.init; +import static xyz.tynn.astring.testing.mockk.MockKt.verify; + +public class AStringAlertDialogTest { + + @MockK + AString aString; + + @MockK + AlertDialog dialog; + + @MockK + OnClickListener listener; + @MockK + Message message; + + @Before + public void setup() { + init(this, true); + } + + @Test + public void setButton_with_listener_should_delegate_to_dialog() { + AStringAlertDialog.setButton(dialog, BUTTON_NEUTRAL, aString, listener); + + verify(() -> dialog.setButton(BUTTON_NEUTRAL, aString.invoke(dialog.getContext()), listener)); + } + + @Test + public void setButton_with_null_listener_should_delegate_to_dialog() { + AStringAlertDialog.setButton(dialog, BUTTON_NEUTRAL, aString, (OnClickListener) null); + + verify(() -> dialog.setButton(BUTTON_NEUTRAL, aString.invoke(dialog.getContext()), (OnClickListener) null)); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setButton_with_listener_should_throw_on_null_dialog() { + AStringAlertDialog.setButton(null, BUTTON_NEUTRAL, aString, listener); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setButton_with_listener_should_throw_on_null_string() { + AStringAlertDialog.setButton(dialog, BUTTON_NEUTRAL, null, listener); + } + + @Test + public void setButton_with_message_should_delegate_to_dialog() { + AStringAlertDialog.setButton(dialog, BUTTON_NEUTRAL, aString, message); + + verify(() -> dialog.setButton(BUTTON_NEUTRAL, aString.invoke(dialog.getContext()), message)); + } + + @Test + public void setButton_with_null_message_should_delegate_to_dialog() { + AStringAlertDialog.setButton(dialog, BUTTON_NEUTRAL, aString, (Message) null); + + verify(() -> dialog.setButton(BUTTON_NEUTRAL, aString.invoke(dialog.getContext()), (Message) null)); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setButton_with_message_should_throw_on_null_dialog() { + AStringAlertDialog.setButton(null, BUTTON_NEUTRAL, aString, message); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setButton_with_message_should_throw_on_null_string() { + AStringAlertDialog.setButton(dialog, BUTTON_NEUTRAL, null, message); + } + + @Test + public void setMessage_should_delegate_to_dialog() { + AStringAlertDialog.setMessage(dialog, aString); + + verify(() -> dialog.setMessage(aString.invoke(dialog.getContext()))); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setMessage_should_throw_on_null_dialog() { + AStringAlertDialog.setMessage(null, aString); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setMessage_should_throw_on_null_string() { + AStringAlertDialog.setMessage(dialog, null); + } + + @Test + public void setTitle_should_delegate_to_dialog() { + AStringAlertDialog.setTitle(dialog, aString); + + verify(() -> dialog.setTitle(aString.invoke(dialog.getContext()))); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setTitle_should_throw_on_null_dialog() { + AStringAlertDialog.setTitle(null, aString); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setTitle_should_throw_on_null_string() { + AStringAlertDialog.setTitle(dialog, null); + } +} diff --git a/core/src/test/java/xyz/tynn/astring/core/AStringToastTest.java b/core/src/test/java/xyz/tynn/astring/core/AStringToastTest.java new file mode 100644 index 0000000..f6aeb92 --- /dev/null +++ b/core/src/test/java/xyz/tynn/astring/core/AStringToastTest.java @@ -0,0 +1,85 @@ +// Copyright 2021 Christian Schmitz +// SPDX-License-Identifier: Apache-2.0 + +package xyz.tynn.astring.core; + +import android.content.Context; +import android.widget.Toast; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import io.mockk.impl.annotations.MockK; +import xyz.tynn.astring.AString; + +import static android.widget.Toast.LENGTH_LONG; +import static org.junit.Assert.assertEquals; +import static xyz.tynn.astring.core.MockkDefinitionsKt.mockkToastMakeText; +import static xyz.tynn.astring.testing.mockk.MockKt.clearAll; +import static xyz.tynn.astring.testing.mockk.MockKt.init; +import static xyz.tynn.astring.testing.mockk.MockKt.prepare; +import static xyz.tynn.astring.testing.mockk.MockKt.verify; + +public class AStringToastTest { + + @MockK + AString aString; + + @MockK + Toast toast; + + @MockK + Context context; + + @Before + public void setup() { + init(this, true); + prepare(Toast.class); + mockkToastMakeText(toast); + } + + @After + public void teardown() { + clearAll(); + } + + @Test + public void makeText_should_delegate_to_toast() { + assertEquals(toast, + AStringToast.makeText(context, aString, LENGTH_LONG)); + + verify(() -> Toast.makeText(context, aString.invoke(context), LENGTH_LONG)); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void makeText_should_throw_on_null_string() { + AStringToast.makeText(context, null, LENGTH_LONG); + } + + @Test + public void setText_should_delegate_to_toast() { + AStringToast.setText(toast, context, aString); + + verify(() -> toast.setText(aString.invoke(context))); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setText_should_throw_on_null_toast() { + AStringToast.setText(null, context, aString); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setText_should_throw_on_null_context() { + AStringToast.setText(toast, null, aString); + } + + @SuppressWarnings("ConstantConditions") + @Test(expected = NullPointerException.class) + public void setText_should_throw_on_null_string() { + AStringToast.setText(toast, context, null); + } +} diff --git a/core/src/test/java/xyz/tynn/astring/core/MockkDefinitions.kt b/core/src/test/java/xyz/tynn/astring/core/MockkDefinitions.kt new file mode 100644 index 0000000..469e056 --- /dev/null +++ b/core/src/test/java/xyz/tynn/astring/core/MockkDefinitions.kt @@ -0,0 +1,13 @@ +// Copyright 2021 Christian Schmitz +// SPDX-License-Identifier: Apache-2.0 + +package xyz.tynn.astring.core + +import android.widget.Toast +import io.mockk.every + +fun mockkToastMakeText( + toast: Toast, +) = every { + Toast.makeText(any(), any(), any()) +} returns toast diff --git a/core/src/test/kotlin/xyz/tynn/astring/core/AStringAlertDialogBuilderKtTest.kt b/core/src/test/kotlin/xyz/tynn/astring/core/AStringAlertDialogBuilderKtTest.kt new file mode 100644 index 0000000..82b28e6 --- /dev/null +++ b/core/src/test/kotlin/xyz/tynn/astring/core/AStringAlertDialogBuilderKtTest.kt @@ -0,0 +1,69 @@ +// Copyright 2021 Christian Schmitz +// SPDX-License-Identifier: Apache-2.0 + +package xyz.tynn.astring.core + +import android.app.AlertDialog +import android.content.DialogInterface.OnClickListener +import io.mockk.mockk +import io.mockk.verify +import xyz.tynn.astring.AString +import kotlin.test.Test +import kotlin.test.assertEquals + +internal class AStringAlertDialogBuilderKtTest { + + val aString = mockk(relaxed = true) + val builder = mockk(relaxed = true) + val listener = mockk() + + @Test + fun `setNegativeButton should delegate to builder`() { + assertEquals( + builder, + builder.setNegativeButton(aString, listener), + ) + + verify { builder.setNegativeButton(aString(builder.context), listener) } + } + + @Test + fun `setNeutralButton should delegate to builder`() { + assertEquals( + builder, + builder.setNeutralButton(aString, listener), + ) + + verify { builder.setNeutralButton(aString(builder.context), listener) } + } + + @Test + fun `setPositiveButton should delegate to builder`() { + assertEquals( + builder, + builder.setPositiveButton(aString, listener), + ) + + verify { builder.setPositiveButton(aString(builder.context), listener) } + } + + @Test + fun `setMessage should delegate to builder`() { + assertEquals( + builder, + builder.setMessage(aString), + ) + + verify { builder.setMessage(aString(builder.context)) } + } + + @Test + fun `setTitle should delegate to builder`() { + assertEquals( + builder, + builder.setTitle(aString), + ) + + verify { builder.setTitle(aString(builder.context)) } + } +} diff --git a/core/src/test/kotlin/xyz/tynn/astring/core/AStringAlertDialogKtTest.kt b/core/src/test/kotlin/xyz/tynn/astring/core/AStringAlertDialogKtTest.kt new file mode 100644 index 0000000..ca5e9a9 --- /dev/null +++ b/core/src/test/kotlin/xyz/tynn/astring/core/AStringAlertDialogKtTest.kt @@ -0,0 +1,51 @@ +// Copyright 2021 Christian Schmitz +// SPDX-License-Identifier: Apache-2.0 + +package xyz.tynn.astring.core + +import android.app.AlertDialog +import android.content.DialogInterface.BUTTON_NEUTRAL +import android.content.DialogInterface.OnClickListener +import android.os.Message +import io.mockk.mockk +import io.mockk.verify +import xyz.tynn.astring.AString +import kotlin.test.Test + +internal class AStringAlertDialogKtTest { + + val aString = mockk(relaxed = true) + val dialog = mockk(relaxed = true) + + @Test + fun `setNegativeButton with listener should delegate to dialog`() { + val listener = mockk() + + dialog.setButton(BUTTON_NEUTRAL, aString, listener) + + verify { dialog.setButton(BUTTON_NEUTRAL, aString(dialog.context), listener) } + } + + @Test + fun `setNegativeButton with message should delegate to dialog`() { + val message = mockk() + + dialog.setButton(BUTTON_NEUTRAL, aString, message) + + verify { dialog.setButton(BUTTON_NEUTRAL, aString(dialog.context), message) } + } + + @Test + fun `setMessage should delegate to dialog`() { + dialog.setMessage(aString) + + verify { dialog.setMessage(aString(dialog.context)) } + } + + @Test + fun `setTitle should delegate to dialog`() { + dialog.setTitle(aString) + + verify { dialog.setTitle(aString(dialog.context)) } + } +} diff --git a/example/common/src/main/kotlin/xyz/tynn/astring/example/common/AStrings.kt b/example/common/src/main/kotlin/xyz/tynn/astring/example/common/AStrings.kt index 722dab7..8b87a68 100644 --- a/example/common/src/main/kotlin/xyz/tynn/astring/example/common/AStrings.kt +++ b/example/common/src/main/kotlin/xyz/tynn/astring/example/common/AStrings.kt @@ -7,13 +7,19 @@ import xyz.tynn.astring.* val aString = "AString".asAString() val accessibilityPaneTitle = "accessibility pane title".asAString() +val action1 = StringResource(R.string.astring_action, 1) +val action2 = StringResource(R.string.astring_action, 2) +val action3 = StringResource(R.string.astring_action, 3) val append = StringResource(R.string.astring_append) val appendRange = StringResource(R.string.astring_append_range, 123) val contentDescription = "content description".asAString() +val dialog = StringResource(R.string.astring_dialog) val error = StringResource(R.string.astring_error) val errorIcon = StringResource(R.string.astring_error_with_icon) val hint = QuantityStringResource(R.plurals.astring_text_hint, 0) +val message = StringResource(R.string.astring_message) val stateDescription = "state description".asAString() val text = TextResource(R.string.astring_text) val textType = QuantityTextResource(R.plurals.astring_text_type, 5) +val title = StringResource(R.string.astring_title) val tooltipText = QuantityStringResource(R.plurals.astring_tooltip_text, 2, 2) diff --git a/example/common/src/main/res/layout/activity_main.xml b/example/common/src/main/res/layout/activity_main.xml index f4fb6de..0ce7f46 100644 --- a/example/common/src/main/res/layout/activity_main.xml +++ b/example/common/src/main/res/layout/activity_main.xml @@ -117,5 +117,41 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/string_text" /> + +