├── .github └── FUNDING.yml ├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── gradle.xml ├── jarRepositories.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── codepalace │ │ └── chatbot │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ │ └── com │ │ │ └── codepalace │ │ │ └── chatbot │ │ │ ├── data │ │ │ └── Message.kt │ │ │ ├── ui │ │ │ ├── MainActivity.kt │ │ │ └── MessagingAdapter.kt │ │ │ └── utils │ │ │ ├── BotResponse.kt │ │ │ ├── Constants.kt │ │ │ ├── SolveMath.kt │ │ │ └── Time.kt │ └── res │ │ ├── drawable │ │ ├── ic_launcher_foreground.xml │ │ ├── receive_round_box.xml │ │ ├── round_button.xml │ │ └── send_round_box.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ └── message_item.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── codepalace │ └── chatbot │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [federicocotogno] 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 20 | 22 | 23 | 24 | 26 | 27 | 28 |
29 | 30 | 31 | 32 | xmlns:android 33 | 34 | ^$ 35 | 36 | 37 | 38 |
39 |
40 | 41 | 42 | 43 | xmlns:.* 44 | 45 | ^$ 46 | 47 | 48 | BY_NAME 49 | 50 |
51 |
52 | 53 | 54 | 55 | .*:id 56 | 57 | http://schemas.android.com/apk/res/android 58 | 59 | 60 | 61 |
62 |
63 | 64 | 65 | 66 | .*:name 67 | 68 | http://schemas.android.com/apk/res/android 69 | 70 | 71 | 72 |
73 |
74 | 75 | 76 | 77 | name 78 | 79 | ^$ 80 | 81 | 82 | 83 |
84 |
85 | 86 | 87 | 88 | style 89 | 90 | ^$ 91 | 92 | 93 | 94 |
95 |
96 | 97 | 98 | 99 | .* 100 | 101 | ^$ 102 | 103 | 104 | BY_NAME 105 | 106 |
107 |
108 | 109 | 110 | 111 | .* 112 | 113 | http://schemas.android.com/apk/res/android 114 | 115 | 116 | ANDROID_ATTRIBUTE_ORDER 117 | 118 |
119 |
120 | 121 | 122 | 123 | .* 124 | 125 | .* 126 | 127 | 128 | BY_NAME 129 | 130 |
131 |
132 |
133 |
134 | 135 | 137 |
138 |
-------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChatBot 2 | This is a sample app that can be used to experiment with creating real chat apps in Android with Kotlin. 3 | 4 | Clone the sample code and play around with the classes and the xml files to create your own ChatBot app! 5 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 30 7 | buildToolsVersion "30.0.0" 8 | 9 | defaultConfig { 10 | applicationId "com.codepalace.chatbot" 11 | minSdkVersion 23 12 | targetSdkVersion 30 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(dir: "libs", include: ["*.jar"]) 29 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 30 | implementation 'androidx.core:core-ktx:1.3.1' 31 | implementation 'androidx.appcompat:appcompat:1.2.0' 32 | implementation 'androidx.constraintlayout:constraintlayout:2.0.1' 33 | testImplementation 'junit:junit:4.12' 34 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 35 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 36 | 37 | //RecyclerView 38 | implementation 'androidx.recyclerview:recyclerview:1.1.0' 39 | 40 | //Coroutines 41 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9' 42 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' 43 | 44 | // Lifecycle components 45 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0" 46 | implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" 47 | implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0" 48 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" 49 | 50 | 51 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/codepalace/chatbot/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.codepalace.chatbot 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.codepalace.chatbot", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indently/ChatBot/7535f31afaa782c17ad6714bb031f61127dfed35/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/codepalace/chatbot/data/Message.kt: -------------------------------------------------------------------------------- 1 | package com.codepalace.chatbot.data 2 | 3 | data class Message(val message: String, val id: String, val time: String) -------------------------------------------------------------------------------- /app/src/main/java/com/codepalace/chatbot/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.codepalace.chatbot.ui 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import androidx.appcompat.app.AppCompatActivity 6 | import android.os.Bundle 7 | import androidx.recyclerview.widget.LinearLayoutManager 8 | import com.codepalace.chatbot.R 9 | import com.codepalace.chatbot.data.Message 10 | import com.codepalace.chatbot.utils.Constants.RECEIVE_ID 11 | import com.codepalace.chatbot.utils.Constants.SEND_ID 12 | import com.codepalace.chatbot.utils.BotResponse 13 | import com.codepalace.chatbot.utils.Constants.OPEN_GOOGLE 14 | import com.codepalace.chatbot.utils.Constants.OPEN_SEARCH 15 | import com.codepalace.chatbot.utils.Time 16 | import kotlinx.android.synthetic.main.activity_main.* 17 | import kotlinx.coroutines.* 18 | 19 | class MainActivity : AppCompatActivity() { 20 | private val TAG = "MainActivity" 21 | 22 | //You can ignore this messageList if you're coming from the tutorial, 23 | // it was used only for my personal debugging 24 | var messagesList = mutableListOf() 25 | 26 | private lateinit var adapter: MessagingAdapter 27 | private val botList = listOf("Peter", "Francesca", "Luigi", "Igor") 28 | 29 | override fun onCreate(savedInstanceState: Bundle?) { 30 | super.onCreate(savedInstanceState) 31 | setContentView(R.layout.activity_main) 32 | 33 | recyclerView() 34 | 35 | clickEvents() 36 | 37 | val random = (0..3).random() 38 | customBotMessage("Hello! Today you're speaking with ${botList[random]}, how may I help?") 39 | } 40 | 41 | private fun clickEvents() { 42 | 43 | //Send a message 44 | btn_send.setOnClickListener { 45 | sendMessage() 46 | } 47 | 48 | //Scroll back to correct position when user clicks on text view 49 | et_message.setOnClickListener { 50 | GlobalScope.launch { 51 | delay(100) 52 | 53 | withContext(Dispatchers.Main) { 54 | rv_messages.scrollToPosition(adapter.itemCount - 1) 55 | 56 | } 57 | } 58 | } 59 | } 60 | 61 | private fun recyclerView() { 62 | adapter = MessagingAdapter() 63 | rv_messages.adapter = adapter 64 | rv_messages.layoutManager = LinearLayoutManager(applicationContext) 65 | 66 | } 67 | 68 | override fun onStart() { 69 | super.onStart() 70 | //In case there are messages, scroll to bottom when re-opening app 71 | GlobalScope.launch { 72 | delay(100) 73 | withContext(Dispatchers.Main) { 74 | rv_messages.scrollToPosition(adapter.itemCount - 1) 75 | } 76 | } 77 | } 78 | 79 | private fun sendMessage() { 80 | val message = et_message.text.toString() 81 | val timeStamp = Time.timeStamp() 82 | 83 | if (message.isNotEmpty()) { 84 | //Adds it to our local list 85 | messagesList.add(Message(message, SEND_ID, timeStamp)) 86 | et_message.setText("") 87 | 88 | adapter.insertMessage(Message(message, SEND_ID, timeStamp)) 89 | rv_messages.scrollToPosition(adapter.itemCount - 1) 90 | 91 | botResponse(message) 92 | } 93 | } 94 | 95 | private fun botResponse(message: String) { 96 | val timeStamp = Time.timeStamp() 97 | 98 | GlobalScope.launch { 99 | //Fake response delay 100 | delay(1000) 101 | 102 | withContext(Dispatchers.Main) { 103 | //Gets the response 104 | val response = BotResponse.basicResponses(message) 105 | 106 | //Adds it to our local list 107 | messagesList.add(Message(response, RECEIVE_ID, timeStamp)) 108 | 109 | //Inserts our message into the adapter 110 | adapter.insertMessage(Message(response, RECEIVE_ID, timeStamp)) 111 | 112 | //Scrolls us to the position of the latest message 113 | rv_messages.scrollToPosition(adapter.itemCount - 1) 114 | 115 | //Starts Google 116 | when (response) { 117 | OPEN_GOOGLE -> { 118 | val site = Intent(Intent.ACTION_VIEW) 119 | site.data = Uri.parse("https://www.google.com/") 120 | startActivity(site) 121 | } 122 | OPEN_SEARCH -> { 123 | val site = Intent(Intent.ACTION_VIEW) 124 | val searchTerm: String? = message.substringAfterLast("search") 125 | site.data = Uri.parse("https://www.google.com/search?&q=$searchTerm") 126 | startActivity(site) 127 | } 128 | 129 | } 130 | } 131 | } 132 | } 133 | 134 | private fun customBotMessage(message: String) { 135 | 136 | GlobalScope.launch { 137 | delay(1000) 138 | withContext(Dispatchers.Main) { 139 | val timeStamp = Time.timeStamp() 140 | messagesList.add(Message(message, RECEIVE_ID, timeStamp)) 141 | adapter.insertMessage(Message(message, RECEIVE_ID, timeStamp)) 142 | 143 | rv_messages.scrollToPosition(adapter.itemCount - 1) 144 | } 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /app/src/main/java/com/codepalace/chatbot/ui/MessagingAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.codepalace.chatbot.ui 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.util.Log 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import androidx.recyclerview.widget.RecyclerView 10 | import com.codepalace.chatbot.R 11 | import com.codepalace.chatbot.data.Message 12 | import com.codepalace.chatbot.utils.Constants.RECEIVE_ID 13 | import com.codepalace.chatbot.utils.Constants.SEND_ID 14 | import kotlinx.android.synthetic.main.message_item.view.* 15 | 16 | class MessagingAdapter: RecyclerView.Adapter() { 17 | 18 | var messagesList = mutableListOf() 19 | 20 | inner class MessageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 21 | init { 22 | itemView.setOnClickListener { 23 | 24 | //Remove message on the item clicked 25 | messagesList.removeAt(adapterPosition) 26 | notifyItemRemoved(adapterPosition) 27 | } 28 | } 29 | } 30 | 31 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageViewHolder { 32 | return MessageViewHolder( 33 | LayoutInflater.from(parent.context).inflate(R.layout.message_item, parent, false) 34 | ) 35 | } 36 | 37 | override fun getItemCount(): Int { 38 | return messagesList.size 39 | } 40 | 41 | @SuppressLint("SetTextI18n") 42 | override fun onBindViewHolder(holder: MessageViewHolder, position: Int) { 43 | val currentMessage = messagesList[position] 44 | 45 | when (currentMessage.id) { 46 | SEND_ID -> { 47 | holder.itemView.tv_message.apply { 48 | text = currentMessage.message 49 | visibility = View.VISIBLE 50 | } 51 | holder.itemView.tv_bot_message.visibility = View.GONE 52 | } 53 | RECEIVE_ID -> { 54 | holder.itemView.tv_bot_message.apply { 55 | text = currentMessage.message 56 | visibility = View.VISIBLE 57 | } 58 | holder.itemView.tv_message.visibility = View.GONE 59 | } 60 | } 61 | } 62 | 63 | fun insertMessage(message: Message) { 64 | this.messagesList.add(message) 65 | notifyItemInserted(messagesList.size) 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /app/src/main/java/com/codepalace/chatbot/utils/BotResponse.kt: -------------------------------------------------------------------------------- 1 | package com.codepalace.chatbot.utils 2 | 3 | import com.codepalace.chatbot.utils.Constants.OPEN_GOOGLE 4 | import com.codepalace.chatbot.utils.Constants.OPEN_SEARCH 5 | import java.sql.Date 6 | import java.sql.Timestamp 7 | import java.text.SimpleDateFormat 8 | 9 | object BotResponse { 10 | 11 | fun basicResponses(_message: String): String { 12 | 13 | val random = (0..2).random() 14 | val message =_message.toLowerCase() 15 | 16 | return when { 17 | 18 | //Flips a coin 19 | message.contains("flip") && message.contains("coin") -> { 20 | val r = (0..1).random() 21 | val result = if (r == 0) "heads" else "tails" 22 | 23 | "I flipped a coin and it landed on $result" 24 | } 25 | 26 | //Math calculations 27 | message.contains("solve") -> { 28 | val equation: String? = message.substringAfterLast("solve") 29 | return try { 30 | val answer = SolveMath.solveMath(equation ?: "0") 31 | "$answer" 32 | 33 | } catch (e: Exception) { 34 | "Sorry, I can't solve that." 35 | } 36 | } 37 | 38 | //Hello 39 | message.contains("hello") -> { 40 | when (random) { 41 | 0 -> "Hello there!" 42 | 1 -> "Sup" 43 | 2 -> "Buongiorno!" 44 | else -> "error" } 45 | } 46 | 47 | //How are you? 48 | message.contains("how are you") -> { 49 | when (random) { 50 | 0 -> "I'm doing fine, thanks!" 51 | 1 -> "I'm hungry..." 52 | 2 -> "Pretty good! How about you?" 53 | else -> "error" 54 | } 55 | } 56 | 57 | //What time is it? 58 | message.contains("time") && message.contains("?")-> { 59 | val timeStamp = Timestamp(System.currentTimeMillis()) 60 | val sdf = SimpleDateFormat("dd/MM/yyyy HH:mm") 61 | val date = sdf.format(Date(timeStamp.time)) 62 | 63 | date.toString() 64 | } 65 | 66 | //Open Google 67 | message.contains("open") && message.contains("google")-> { 68 | OPEN_GOOGLE 69 | } 70 | 71 | //Search on the internet 72 | message.contains("search")-> { 73 | OPEN_SEARCH 74 | } 75 | 76 | //When the programme doesn't understand... 77 | else -> { 78 | when (random) { 79 | 0 -> "I don't understand..." 80 | 1 -> "Try asking me something different" 81 | 2 -> "Idk" 82 | else -> "error" 83 | } 84 | } 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /app/src/main/java/com/codepalace/chatbot/utils/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.codepalace.chatbot.utils 2 | 3 | object Constants { 4 | 5 | const val SEND_ID = "SEND_ID" 6 | const val RECEIVE_ID = "RECEIVE_ID" 7 | 8 | const val OPEN_GOOGLE = "Opening Google..." 9 | const val OPEN_SEARCH = "Searching..." 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/codepalace/chatbot/utils/SolveMath.kt: -------------------------------------------------------------------------------- 1 | package com.codepalace.chatbot.utils 2 | 3 | import android.util.Log 4 | 5 | object SolveMath { 6 | 7 | fun solveMath(equation: String) : Int{ 8 | 9 | val newEquation = equation.replace(" ", "") 10 | Log.d("Math", newEquation) 11 | 12 | return when { 13 | newEquation.contains("+") -> { 14 | val split = newEquation.split("+") 15 | val result = split[0].toInt() + split[1].toInt() 16 | result 17 | } 18 | newEquation.contains("-") -> { 19 | val split = newEquation.split("-") 20 | val result = split[0].toInt() - split[1].toInt() 21 | result 22 | } 23 | newEquation.contains("*") -> { 24 | val split = newEquation.split("*") 25 | val result = split[0].toInt() * split[1].toInt() 26 | result 27 | } 28 | newEquation.contains("/") -> { 29 | val split = newEquation.split("/") 30 | val result = split[0].toInt() / split[1].toInt() 31 | result 32 | } 33 | else -> { 34 | 0 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/codepalace/chatbot/utils/Time.kt: -------------------------------------------------------------------------------- 1 | package com.codepalace.chatbot.utils 2 | 3 | import java.sql.Date 4 | import java.sql.Timestamp 5 | import java.text.SimpleDateFormat 6 | 7 | object Time { 8 | 9 | fun timeStamp(): String { 10 | 11 | val timeStamp = Timestamp(System.currentTimeMillis()) 12 | val sdf = SimpleDateFormat("HH:mm") 13 | val time = sdf.format(Date(timeStamp.time)) 14 | 15 | return time.toString() 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/receive_round_box.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/send_round_box.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 17 | 18 | 30 | 31 |