Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I60 Connected Android to server #74

Merged
merged 31 commits into from
Feb 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
02e0ed7
Removed firebase folder
Feb 1, 2019
1cb992e
Removed firebase functions deployment from travis.yaml
oktay-sen Feb 2, 2019
a8df289
Random IntelliJ changes
oktay-sen Feb 2, 2019
aa62dcb
Made initial hello world server
oktay-sen Feb 2, 2019
342300e
Added more packages
oktay-sen Feb 2, 2019
b843945
Minor change to README
oktay-sen Feb 2, 2019
2bf2d17
Minor change to README
oktay-sen Feb 2, 2019
ffc9114
Basic implementation of a CRUD server with mongodb
oktay-sen Feb 2, 2019
9e34bf8
Added Bonjour Publisher and Client
freddiejbawden Feb 2, 2019
95129ba
added bonjour
Feb 2, 2019
80e9d1a
Robot comms teset
Feb 3, 2019
dd4cc58
WIP makefile
oktay-sen Feb 3, 2019
ac21271
Merge branch 'beta' into i58
oktay-sen Feb 3, 2019
0f8768e
Re-added package-lock.json
oktay-sen Feb 3, 2019
420cf07
Robot turns on when button pressed
freddiejbawden Feb 4, 2019
2ef45f9
Merge branch 'i58' of https://github.com/Assis10t/assis10t into i58
freddiejbawden Feb 4, 2019
98a4e95
Android can now find server via zeroconf
oktay-sen Feb 4, 2019
f16ada6
Added test mode for when disconnected from robot
Feb 4, 2019
f6e88f2
Refactored ServerConnection
oktay-sen Feb 4, 2019
cc9648f
Merge remote-tracking branch 'origin' into i60
oktay-sen Feb 4, 2019
b8d7aa7
Merge remote branch into i58
Feb 4, 2019
d4bb2bd
Updated server name in android
Feb 4, 2019
12a7f19
Added turn off on
Feb 4, 2019
60f7b5b
Fixed minor things
oktay-sen Feb 4, 2019
43e5746
App can display list of items
oktay-sen Feb 4, 2019
cee62ba
Merge beta into i60
oktay-sen Feb 4, 2019
908332f
Re-added addItem and getItems
oktay-sen Feb 4, 2019
7b2972f
Fixed app not connecting to server
oktay-sen Feb 4, 2019
66c16e1
Added ability to refresh list of items
oktay-sen Feb 4, 2019
ddb6bb6
Implemented adding order on android
oktay-sen Feb 4, 2019
f3a4327
Adding order now removes the items in it from the database
oktay-sen Feb 4, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea
5 changes: 4 additions & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ android {
compileSdkVersion 28
defaultConfig {
applicationId "io.github.assis10t.bobandroid"
minSdkVersion 22
minSdkVersion 23
targetSdkVersion 28
versionCode 1
versionName "1.0"
Expand All @@ -29,10 +29,13 @@ dependencies {
implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.android.support:support-v4:28.0.0'
implementation 'com.jakewharton.timber:timber:4.7.1'
testImplementation "com.nhaarman:mockito-kotlin:1.1.0"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'org.jmdns:jmdns:3.5.1'
implementation 'com.squareup.okhttp3:okhttp:3.12.0'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'org.jetbrains.anko:anko-common:0.9'
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
package io.github.assis10t.bobandroid

import android.app.Application
import timber.log.Timber

class Application: Application() {

override fun onCreate() {
super.onCreate()
ServerConnection.initialize()

//From: https://github.com/oktay-sen/Coinz
Timber.plant(object : Timber.DebugTree() {
override fun createStackElementTag(element: StackTraceElement): String? {
return super.createStackElementTag(element) + ':' + element.lineNumber
}
})
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package io.github.assis10t.bobandroid

import android.content.Context
import android.net.nsd.NsdManager
import android.net.nsd.NsdServiceInfo
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import java.net.InetAddress
import android.support.v7.widget.CardView
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import io.github.assis10t.bobandroid.pojo.Item
import io.github.assis10t.bobandroid.pojo.Order
import kotlinx.android.synthetic.main.activity_main.*
import timber.log.Timber

class MainActivity : AppCompatActivity() {

Expand All @@ -15,5 +21,97 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

container.isRefreshing = true
container.setOnRefreshListener { refreshItems() }
ServerConnection().connect {
container.isRefreshing = false
}
item_list.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
item_list.adapter = ItemAdapter { selected ->
if (selected.isEmpty())
make_order.hide()
else
make_order.show()
}

//make_order.hide()
make_order.setOnClickListener {
container.isRefreshing = true
make_order.hide()
val adapter = item_list.adapter as ItemAdapter
ServerConnection().makeOrder(Order(null, adapter.selectedItems)) { success ->
if (!success)
Timber.e("Could not make order.")
else {
Timber.d("Order made.")
refreshItems()
}
}
}

refreshItems()
}

fun refreshItems() {
container.isRefreshing = true
ServerConnection().getItems { success, items ->
container.isRefreshing = false
if (!success) {
Timber.e("getItems failed.")
return@getItems
}
Timber.d("GetItems success. Items: ${items?.size}")
val adapter = item_list.adapter as ItemAdapter
adapter.updateItems(items!!)
}
}

class ItemAdapter(var onSelectionChanged: (selected: List<Item>) -> Unit): RecyclerView.Adapter<ItemAdapter.ViewHolder>() {
var itemList: List<Item> = listOf()
val selectedItems: MutableList<Item> = mutableListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.fragment_shop_item, parent, false)
return ViewHolder(view)
}

override fun getItemCount(): Int = itemList.size

override fun onBindViewHolder(vh: ViewHolder, pos: Int) {
val item = itemList[pos]
val context = vh.container.context
vh.title.text = item.name
vh.container.setCardBackgroundColor(
if (selectedItems.contains(item))
vh.container.context.getColor(R.color.selectHighlight)
else
vh.container.context.getColor(R.color.white)
)
vh.container.cardElevation =
if (selectedItems.contains(item))
dp(context, 4f)
else
dp(context, 1f)
vh.container.setOnClickListener {
if (selectedItems.contains(item))
selectedItems.remove(item)
else
selectedItems.add(item)
onSelectionChanged(itemList)
notifyItemChanged(pos)
}
}

fun updateItems(items: List<Item>) {
this.itemList = items
this.selectedItems.clear()
notifyDataSetChanged()
}

class ViewHolder(view: View): RecyclerView.ViewHolder(view) {
val title: TextView = view.findViewById(R.id.title)
val quantity: TextView = view.findViewById(R.id.quantity)
val container: CardView = view.findViewById(R.id.container)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,60 +1,148 @@
package io.github.assis10t.bobandroid

import android.os.AsyncTask
import android.util.Log
import java.net.Inet4Address
import com.google.gson.Gson
import io.github.assis10t.bobandroid.pojo.GetItemsResponse
import io.github.assis10t.bobandroid.pojo.Item
import io.github.assis10t.bobandroid.pojo.Order
import okhttp3.MediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread
import timber.log.Timber
import java.io.IOException
import java.util.concurrent.TimeUnit
import javax.jmdns.JmDNS
import javax.jmdns.ServiceEvent
import javax.jmdns.ServiceListener

class ServerConnection {

companion object {
private val TAG = "ServerConnection"
val SERVER_NAME = "assis10t"
var serverIp: String? = null
var serverAddress: String? = null
val httpClient: OkHttpClient = OkHttpClient()

val onConnectedListeners: MutableList<(String) -> Unit> = mutableListOf()
val onConnectedListeners: MutableList<(serverAddress: String) -> Unit> = mutableListOf()

class ConnectTask: AsyncTask<Unit, JmDNS, Unit>() {
override fun doInBackground(vararg params: Unit?) {
Log.d(TAG, "Discovery started")
fun initialize() {
doAsync {
Timber.d("Discovery started")
val mJmDNS = JmDNS.create()
mJmDNS.addServiceListener("_http._tcp.local.", object : ServiceListener {

override fun serviceResolved(event: ServiceEvent?) {
val info = mJmDNS.getServiceInfo(event!!.type, event.name)
Log.d(TAG, "Service resolved: $info")
Timber.d("Service resolved: $info")
if (info.name.contains(SERVER_NAME)) {
serverIp = "${info.inet4Addresses[0]!!.hostAddress}:${info.port}"
onConnectedListeners.forEach { it(serverIp!!) }
onConnectedListeners.clear()
uiThread {
serverAddress = "https://${info.inet4Addresses[0]!!.hostAddress}:${info.port}"
onConnectedListeners.forEach { it(serverAddress!!) }
onConnectedListeners.clear()
}
}
}

override fun serviceRemoved(event: ServiceEvent?) {
Log.d(TAG, "Service removed")
Timber.d("Service removed")
}

override fun serviceAdded(event: ServiceEvent?) {
val info = mJmDNS.getServiceInfo(event!!.type, event.name)
Log.d(TAG, "Service added: $info")
Timber.d("Service added: $info")
}
})
}
}

fun initialize() {
ConnectTask().execute()
ServerConnection().connect { ip ->
Log.d(TAG, "Server found at $ip")
Timber.d("Server found at $ip")
}
}
}

fun connect(onConnected: (String) -> Unit) {
if (serverIp != null)
onConnected(serverIp!!)
if (serverAddress != null)
onConnected(serverAddress!!)
else
onConnectedListeners.add(onConnected)
}

val getRequestFactory = { http: OkHttpClient ->
{ url: String, onGetComplete: (success: Boolean, response: String?) -> Unit ->
doAsync {
Timber.d("Get request to $url")
try {
val request = Request.Builder().url(url).build()
val response = http.newCall(request).execute()
Timber.d("Response received.")
if (!response.isSuccessful) {
Timber.e("Get Request failed: (${response.code()}) ${response.body().toString()}")
uiThread { onGetComplete(false, null) }
} else {
uiThread { onGetComplete(true, response.body()?.string()) }
}
} catch (e: IOException) {
Timber.e(e, "Get Request failed")
uiThread { onGetComplete(false, null) }
}
}
}
}

val postRequestFactory = { http: OkHttpClient, gson: Gson ->
{ url: String, body: Any, onPostComplete: (success: Boolean, response: String?) -> Unit ->
doAsync {
Timber.d("Post request to $url")
try {
val JSON = MediaType.get("application/json; charset=utf-8")
val requestBody = RequestBody.create(JSON, gson.toJson(body))
val request = Request.Builder().url(url).post(requestBody).build()
val response = http.newCall(request).execute()
Timber.d("Response received.")
if (!response.isSuccessful) {
Timber.e("Post Request failed: (${response.code()}) ${response.body().toString()}")
uiThread { onPostComplete(false, null) }
} else {
uiThread { onPostComplete(true, response.body()?.string()) }
}
} catch (e: IOException) {
Timber.e(e, "Post Request failed")
uiThread { onPostComplete(false, null) }
}
}
}
}

val getItemsFactory = { http: OkHttpClient, gson: Gson ->
{ onGetItems: (success: Boolean, items: List<Item>?) -> Unit ->
connect { server ->
getRequestFactory(http)("$server/items") { success, str ->
Timber.d("Result: $success, response: $str")
if (!success) {
onGetItems(success, null)
} else {
val response = gson.fromJson(str!!, GetItemsResponse::class.java)
onGetItems(response.success, response.items)
}
}
}
}
}
val getItems = getItemsFactory(httpClient, Gson())

val makeOrderFactory = { http: OkHttpClient, gson: Gson ->
{ order: Order, onOrderComplete: ((success: Boolean) -> Unit)? ->
connect { server ->
postRequestFactory(http, gson)("$server/order", order) { success, str ->
Timber.d("Result: $success, response: $str")
if (!success) {
onOrderComplete?.invoke(success)
} else {
val response = gson.fromJson(str!!, GetItemsResponse::class.java)
onOrderComplete?.invoke(response.success)
}
}
}
}
}
val makeOrder = makeOrderFactory(httpClient, Gson())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package io.github.assis10t.bobandroid.pojo

class GetItemsResponse(val success: Boolean = false, val items:List<Item> = listOf())
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.github.assis10t.bobandroid.pojo

class Item (
val _id:String? = null,
val name:String? = null
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.github.assis10t.bobandroid.pojo

class Order(
val _id: String? = null,
val items: List<Item> = listOf()
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.github.assis10t.bobandroid

import android.content.Context
import android.util.TypedValue

fun dp(context: Context, dp: Float) =
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.resources.displayMetrics)
9 changes: 9 additions & 0 deletions android/app/src/main/res/drawable/ic_send_black_24dp.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="https://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
</vector>
Loading