From f48229d21648d759565137c989365e4fe04012c4 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Sat, 1 Feb 2020 20:43:56 -0800 Subject: [PATCH] Add application files --- app/.gitignore | 1 + app/build.gradle | 38 ++++ app/proguard-rules.pro | 21 +++ .../ExampleInstrumentedTest.kt | 24 +++ app/src/main/AndroidManifest.xml | 24 +++ .../fencelessgrazing/CollarListActivity.kt | 29 +++ .../fencelessgrazing/CollarSummaryAdapter.kt | 60 +++++++ .../fencelessgrazing/CollarViewHolder.kt | 17 ++ .../danilafe/fencelessgrazing/MainActivity.kt | 39 ++++ .../fencelessgrazing/model/CollarSummary.kt | 4 + .../fencelessgrazing/model/LoginResult.kt | 3 + .../requests/CollarRequest.kt | 25 +++ .../fencelessgrazing/requests/GsonListener.kt | 18 ++ .../fencelessgrazing/requests/LoginRequest.kt | 22 +++ .../drawable-v24/ic_launcher_foreground.xml | 34 ++++ .../res/drawable/ic_launcher_background.xml | 170 ++++++++++++++++++ .../main/res/layout/activity_collar_list.xml | 16 ++ app/src/main/res/layout/activity_main.xml | 45 +++++ .../main/res/layout/collar_summary_layout.xml | 25 +++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2963 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4905 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2060 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2783 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4490 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 6895 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6387 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10413 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9128 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15132 bytes app/src/main/res/values/colors.xml | 6 + app/src/main/res/values/strings.xml | 8 + app/src/main/res/values/styles.xml | 11 ++ .../fencelessgrazing/ExampleUnitTest.kt | 17 ++ 35 files changed, 667 insertions(+) create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/com/danilafe/fencelessgrazing/ExampleInstrumentedTest.kt create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/com/danilafe/fencelessgrazing/CollarListActivity.kt create mode 100644 app/src/main/java/com/danilafe/fencelessgrazing/CollarSummaryAdapter.kt create mode 100644 app/src/main/java/com/danilafe/fencelessgrazing/CollarViewHolder.kt create mode 100644 app/src/main/java/com/danilafe/fencelessgrazing/MainActivity.kt create mode 100644 app/src/main/java/com/danilafe/fencelessgrazing/model/CollarSummary.kt create mode 100644 app/src/main/java/com/danilafe/fencelessgrazing/model/LoginResult.kt create mode 100644 app/src/main/java/com/danilafe/fencelessgrazing/requests/CollarRequest.kt create mode 100644 app/src/main/java/com/danilafe/fencelessgrazing/requests/GsonListener.kt create mode 100644 app/src/main/java/com/danilafe/fencelessgrazing/requests/LoginRequest.kt create mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/layout/activity_collar_list.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/collar_summary_layout.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/test/java/com/danilafe/fencelessgrazing/ExampleUnitTest.kt diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..c140eb5 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,38 @@ +apply plugin: 'com.android.application' + +apply plugin: 'kotlin-android' + +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.2" + defaultConfig { + applicationId "com.danilafe.fencelessgrazing" + minSdkVersion 19 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'androidx.core:core-ktx:1.0.2' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'com.android.volley:volley:1.1.1' + implementation 'com.google.code.gson:gson:2.8.6' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + implementation 'androidx.recyclerview:recyclerview:1.1.0' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/danilafe/fencelessgrazing/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/danilafe/fencelessgrazing/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..9734bbc --- /dev/null +++ b/app/src/androidTest/java/com/danilafe/fencelessgrazing/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.danilafe.fencelessgrazing + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.danilafe.fencelessgrazing", appContext.packageName) + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..be4b457 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/danilafe/fencelessgrazing/CollarListActivity.kt b/app/src/main/java/com/danilafe/fencelessgrazing/CollarListActivity.kt new file mode 100644 index 0000000..3a62cc9 --- /dev/null +++ b/app/src/main/java/com/danilafe/fencelessgrazing/CollarListActivity.kt @@ -0,0 +1,29 @@ +package com.danilafe.fencelessgrazing + +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView + +class CollarListActivity : AppCompatActivity() { + + private lateinit var token: String + private lateinit var summaryAdapter: CollarSummaryAdapter + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_collar_list) + + val collarList: RecyclerView = findViewById(R.id.collarSummaryList) + val layoutManager = LinearLayoutManager(collarList.context) + token = intent.getStringExtra("token")!! + summaryAdapter = CollarSummaryAdapter(collarList.context, getString(R.string.apiUrl), token) + + collarList.adapter = summaryAdapter + collarList.layoutManager = layoutManager + collarList.addItemDecoration(DividerItemDecoration(collarList.context, layoutManager.orientation)) + summaryAdapter.triggerRefresh() + } + +} diff --git a/app/src/main/java/com/danilafe/fencelessgrazing/CollarSummaryAdapter.kt b/app/src/main/java/com/danilafe/fencelessgrazing/CollarSummaryAdapter.kt new file mode 100644 index 0000000..b20caf1 --- /dev/null +++ b/app/src/main/java/com/danilafe/fencelessgrazing/CollarSummaryAdapter.kt @@ -0,0 +1,60 @@ +package com.danilafe.fencelessgrazing + +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.Toast +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.DiffUtil +import com.android.volley.Response +import com.android.volley.toolbox.Volley +import com.danilafe.fencelessgrazing.model.CollarSummary +import com.danilafe.fencelessgrazing.requests.CollarRequest + +class CollarSummaryAdapter( + private val context: Context, + private val baseUrl: String, + private val token: String +) : ListAdapter(DiffCallback()) { + + class DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: CollarSummary, newItem: CollarSummary): Boolean + = oldItem.id == newItem.id + + override fun areContentsTheSame(oldItem: CollarSummary, newItem: CollarSummary): Boolean + = oldItem == newItem + } + + private val requestQueue = Volley.newRequestQueue(context) + private val items = mutableListOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CollarViewHolder { + val layout = LayoutInflater.from(parent.context).inflate(viewType, parent, false) + return CollarViewHolder(layout) + } + + override fun onBindViewHolder(holder: CollarViewHolder, position: Int) { + holder.bindData(getItem(position)) + } + + override fun getItemViewType(position: Int): Int = R.layout.collar_summary_layout + + override fun getItem(position: Int): CollarSummary = items[position] + + override fun getItemCount(): Int = items.size + + fun triggerRefresh() { + val request = CollarRequest(baseUrl, token, + Response.Listener { + items.clear() + items.addAll(it) + notifyDataSetChanged() + }, + Response.ErrorListener { + Toast.makeText(context, "Failed to retrieve collar list!", Toast.LENGTH_SHORT).show() + } + ) + requestQueue.add(request) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/danilafe/fencelessgrazing/CollarViewHolder.kt b/app/src/main/java/com/danilafe/fencelessgrazing/CollarViewHolder.kt new file mode 100644 index 0000000..fac8a10 --- /dev/null +++ b/app/src/main/java/com/danilafe/fencelessgrazing/CollarViewHolder.kt @@ -0,0 +1,17 @@ +package com.danilafe.fencelessgrazing + +import android.view.View +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.danilafe.fencelessgrazing.model.CollarSummary + +class CollarViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + private val nameView: TextView = itemView.findViewById(R.id.collarSummaryName) + private val positionView: TextView = itemView.findViewById(R.id.collarSummaryPos) + + fun bindData(summary: CollarSummary) { + nameView.text = summary.name + // TODO figure out how to get getString here. + positionView.text = "Currently at ${summary.pos.longitude}, ${summary.pos.latitude}" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/danilafe/fencelessgrazing/MainActivity.kt b/app/src/main/java/com/danilafe/fencelessgrazing/MainActivity.kt new file mode 100644 index 0000000..8450e0f --- /dev/null +++ b/app/src/main/java/com/danilafe/fencelessgrazing/MainActivity.kt @@ -0,0 +1,39 @@ +package com.danilafe.fencelessgrazing + +import android.content.Intent +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.view.View +import android.widget.TextView +import android.widget.Toast +import com.android.volley.Response +import com.android.volley.toolbox.Volley +import com.danilafe.fencelessgrazing.requests.LoginRequest + +class MainActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + } + + fun attemptLogin(view: View) { + val usernameField: TextView = findViewById(R.id.username) + val passwordField: TextView = findViewById(R.id.password) + val requestQueue = Volley.newRequestQueue(this) + + val loginRequest = + LoginRequest(getString(R.string.apiUrl), + usernameField.text.toString(), passwordField.text.toString(), + Response.Listener { + val newIntent = Intent(this, CollarListActivity::class.java).apply { + putExtra("token", it?.token) + } + startActivity(newIntent) + }, + Response.ErrorListener { + Toast.makeText(this, "Failed to log in! $it", Toast.LENGTH_LONG).show() + }) + requestQueue.add(loginRequest) + } +} diff --git a/app/src/main/java/com/danilafe/fencelessgrazing/model/CollarSummary.kt b/app/src/main/java/com/danilafe/fencelessgrazing/model/CollarSummary.kt new file mode 100644 index 0000000..c885320 --- /dev/null +++ b/app/src/main/java/com/danilafe/fencelessgrazing/model/CollarSummary.kt @@ -0,0 +1,4 @@ +package com.danilafe.fencelessgrazing.model + +data class CollarPos(val longitude: String, val latitude: String) +data class CollarSummary(val id: Int, val name: String, val pos: CollarPos) diff --git a/app/src/main/java/com/danilafe/fencelessgrazing/model/LoginResult.kt b/app/src/main/java/com/danilafe/fencelessgrazing/model/LoginResult.kt new file mode 100644 index 0000000..b76ad2e --- /dev/null +++ b/app/src/main/java/com/danilafe/fencelessgrazing/model/LoginResult.kt @@ -0,0 +1,3 @@ +package com.danilafe.fencelessgrazing.model + +data class LoginResult(val token: String) \ No newline at end of file diff --git a/app/src/main/java/com/danilafe/fencelessgrazing/requests/CollarRequest.kt b/app/src/main/java/com/danilafe/fencelessgrazing/requests/CollarRequest.kt new file mode 100644 index 0000000..5145a71 --- /dev/null +++ b/app/src/main/java/com/danilafe/fencelessgrazing/requests/CollarRequest.kt @@ -0,0 +1,25 @@ +package com.danilafe.fencelessgrazing.requests + +import com.android.volley.Response +import com.android.volley.toolbox.StringRequest +import com.danilafe.fencelessgrazing.model.CollarSummary +import com.google.gson.reflect.TypeToken + +class CollarRequest( + baseUrl: String, + private val token : String, + listener: Response.Listener>, + error: Response.ErrorListener +) : StringRequest( + Method.GET, "${baseUrl}/collars", + GsonListener( + object : TypeToken>() {}.type, + listener + ), error +) { + + override fun getHeaders(): MutableMap { + return mutableMapOf("Authorization" to "Bearer $token") + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/danilafe/fencelessgrazing/requests/GsonListener.kt b/app/src/main/java/com/danilafe/fencelessgrazing/requests/GsonListener.kt new file mode 100644 index 0000000..a66dcc9 --- /dev/null +++ b/app/src/main/java/com/danilafe/fencelessgrazing/requests/GsonListener.kt @@ -0,0 +1,18 @@ +package com.danilafe.fencelessgrazing.requests + +import android.util.Log +import com.android.volley.Response +import com.google.gson.Gson +import java.lang.reflect.Type + +class GsonListener(private val targetType : Type, private val stringListener : Response.Listener) : Response.Listener { + + private val gson = Gson() + + override fun onResponse(response: String?) { + Log.i("Response", response) + if(response == null) { return stringListener.onResponse(null) } + stringListener.onResponse(gson.fromJson(response, targetType)) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/danilafe/fencelessgrazing/requests/LoginRequest.kt b/app/src/main/java/com/danilafe/fencelessgrazing/requests/LoginRequest.kt new file mode 100644 index 0000000..0c9c53b --- /dev/null +++ b/app/src/main/java/com/danilafe/fencelessgrazing/requests/LoginRequest.kt @@ -0,0 +1,22 @@ +package com.danilafe.fencelessgrazing.requests + +import com.android.volley.Response +import com.android.volley.toolbox.StringRequest +import com.danilafe.fencelessgrazing.model.LoginResult + +class LoginRequest( + baseUrl: String, + private val username: String, + private val password: String, + listener: Response.Listener, + error: Response.ErrorListener +) : StringRequest ( + Method.POST, "${baseUrl}/login", + GsonListener(LoginResult::class.java, listener), error +) { + + override fun getParams(): MutableMap { + return mutableMapOf("username" to username, "password" to password) + } + +} \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..1f6bb29 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..0d025f9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_collar_list.xml b/app/src/main/res/layout/activity_collar_list.xml new file mode 100644 index 0000000..1d49c14 --- /dev/null +++ b/app/src/main/res/layout/activity_collar_list.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..a881b5a --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,45 @@ + + + + + + + +