Skip to content

Commit

Permalink
history list view (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
morfeusys authored Dec 30, 2019
1 parent b293532 commit acff450
Show file tree
Hide file tree
Showing 11 changed files with 229 additions and 25 deletions.
10 changes: 9 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
id("com.android.application")
kotlin("android")
id("kotlin-android-extensions")
id("kotlin-kapt")
}

android {
Expand Down Expand Up @@ -51,7 +52,9 @@ repositories {

dependencies {
implementation("androidx.appcompat:appcompat:1.1.0")
implementation("androidx.recyclerview:recyclerview:1.0.0")
implementation("androidx.cardview:cardview:1.0.0")

implementation("androidx.recyclerview:recyclerview:1.1.0")
implementation("androidx.constraintlayout:constraintlayout:1.1.3")
implementation("androidx.core:core-ktx:1.1.0")
implementation("androidx.lifecycle:lifecycle-extensions:2.1.0")
Expand All @@ -64,4 +67,9 @@ dependencies {
implementation("com.justai.aimybox:google-platform-speechkit:0.7.0")

implementation("com.squareup.retrofit2:retrofit:2.5.0")

implementation("androidx.room:room-runtime:2.2.3")
kapt("androidx.room:room-compiler:2.2.3")

implementation("com.ironsource.aura.oneadapter:oneadapter:1.4.0")
}
56 changes: 52 additions & 4 deletions app/src/main/java/com/porfir/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@ package com.porfir

import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import com.idanatz.oneadapter.OneAdapter
import com.idanatz.oneadapter.external.event_hooks.ClickEventHook
import com.idanatz.oneadapter.internal.holders.ViewBinder
import com.justai.aimybox.components.AimyboxAssistantFragment
import com.porfir.dao.PorfirDatabase
import com.porfir.model.HistoryItem
import com.porfir.module.HistoryModule
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.consume
Expand All @@ -14,22 +22,37 @@ class MainActivity : AppCompatActivity(), CoroutineScope {

override val coroutineContext = Dispatchers.Main

private lateinit var promptLayout: View
private lateinit var recyclerView: RecyclerView
private lateinit var recyclerAdapter: OneAdapter
private lateinit var database: PorfirDatabase

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.layout_activity_main)

window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
promptLayout = findViewById(R.id.prompt_layout)
recyclerView = findViewById(R.id.recycler_view)
recyclerAdapter = OneAdapter(recyclerView)
.attachItemModule(HistoryModule().addEventHook(historyClickHook))

val assistantFragment = AimyboxAssistantFragment()
database = (application as PorfirApplication).database

window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)

supportFragmentManager.beginTransaction().apply {
replace(R.id.assistant_container, assistantFragment)
replace(R.id.assistant_container, AimyboxAssistantFragment())
commit()
}

onNewIntent(intent)
}

override fun onResume() {
super.onResume()
refreshRecyclerViewData()
}

override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if (intent?.action == Intent.ACTION_ASSIST) {
Expand All @@ -46,7 +69,32 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
override fun onBackPressed() {
val assistantFragment = (supportFragmentManager.findFragmentById(R.id.assistant_container)
as? AimyboxAssistantFragment)
if (assistantFragment?.onBackPressed() != true) super.onBackPressed()
if (assistantFragment?.onBackPressed() == true) {
refreshRecyclerViewData()
} else {
super.onBackPressed()
}
}

private fun refreshRecyclerViewData() = launch(Dispatchers.IO) {
val items = database.historyDao().getAll()
recyclerAdapter.setItems(items)

promptLayout.visibility = if (items.isEmpty()) View.VISIBLE else View.GONE
recyclerView.visibility = if (items.isNotEmpty()) View.VISIBLE else View.GONE
}

private val historyClickHook = object : ClickEventHook<HistoryItem>() {
override fun onClick(model: HistoryItem, viewBinder: ViewBinder) {
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, model.text)
type = "text/plain"
}

val shareIntent = Intent.createChooser(sendIntent, null)
startActivity(shareIntent)
}
}

}
42 changes: 41 additions & 1 deletion app/src/main/java/com/porfir/PorfirApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,63 @@ package com.porfir

import android.app.Application
import android.content.Context
import androidx.room.Room
import com.justai.aimybox.Aimybox
import com.justai.aimybox.api.DialogApi
import com.justai.aimybox.components.AimyboxProvider
import com.justai.aimybox.core.Config
import com.justai.aimybox.model.reply.TextReply
import com.justai.aimybox.speechkit.google.platform.GooglePlatformSpeechToText
import com.justai.aimybox.speechkit.google.platform.GooglePlatformTextToSpeech
import com.porfir.api.PorfirDialogApi
import com.porfir.dao.PorfirDatabase
import com.porfir.model.HistoryItem
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.launch
import java.util.*

class PorfirApplication: Application(), AimyboxProvider {
class PorfirApplication: Application(), AimyboxProvider, CoroutineScope {

val database by lazy { createDatabase(this) }

override val aimybox by lazy { createAimybox(this) }

override val coroutineContext = Dispatchers.IO

private fun createAimybox(context: Context): Aimybox {
val textToSpeech = GooglePlatformTextToSpeech(context, Locale("ru"))
val speechToText = GooglePlatformSpeechToText(context, Locale("ru"))

val dialogApi = PorfirDialogApi()

return Aimybox(Config.create(speechToText, textToSpeech, dialogApi))
.also { subscribeToDialogApi(it) }
}

private fun createDatabase(context: Context): PorfirDatabase {
return Room.databaseBuilder(
context,
PorfirDatabase::class.java,
"porfir.db").build()
}

private fun subscribeToDialogApi(aimybox: Aimybox) {
val channel = aimybox.dialogApiEvents.openSubscription()
launch {
channel.consumeEach { event ->
event
.takeIf { it is DialogApi.Event.ResponseReceived }
?.also { it ->
val now = Calendar.getInstance().timeInMillis
val response = (it as DialogApi.Event.ResponseReceived).response
response.replies
.filterIsInstance(TextReply::class.java)
.map { reply -> HistoryItem(0, reply.text, now) }
.forEach { item -> database.historyDao().insert(item) }
}
}
}
}
}
16 changes: 16 additions & 0 deletions app/src/main/java/com/porfir/dao/HistoryDao.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.porfir.dao

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.porfir.model.HistoryItem

@Dao
interface HistoryDao {
@Query("SELECT * FROM history_items ORDER BY timestamp DESC")
fun getAll(): List<HistoryItem>

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(item: HistoryItem)
}
10 changes: 10 additions & 0 deletions app/src/main/java/com/porfir/dao/PorfirDatabase.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.porfir.dao

import androidx.room.Database
import androidx.room.RoomDatabase
import com.porfir.model.HistoryItem

@Database(entities = [HistoryItem::class], version = 1, exportSchema = false)
abstract class PorfirDatabase: RoomDatabase() {
abstract fun historyDao(): HistoryDao
}
20 changes: 20 additions & 0 deletions app/src/main/java/com/porfir/model/HistoryItem.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.porfir.model

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.idanatz.oneadapter.external.interfaces.Diffable

@Entity(tableName = "history_items")
data class HistoryItem(
@PrimaryKey(autoGenerate = true)
val id: Long,
@ColumnInfo(name = "generated_text")
val text: String,
@ColumnInfo(name = "timestamp")
val timestamp: Long
): Diffable {
override fun areContentTheSame(other: Any) = other is HistoryItem && other.id == id

override fun getUniqueIdentifier(): Long = id
}
20 changes: 20 additions & 0 deletions app/src/main/java/com/porfir/module/HistoryModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.porfir.module

import android.widget.TextView
import com.idanatz.oneadapter.external.modules.ItemModule
import com.idanatz.oneadapter.external.modules.ItemModuleConfig
import com.idanatz.oneadapter.internal.holders.ViewBinder
import com.porfir.R
import com.porfir.model.HistoryItem

class HistoryModule: ItemModule<HistoryItem>() {

override fun provideModuleConfig() = object : ItemModuleConfig() {
override fun withLayoutResource() = R.layout.history_item
}

override fun onBind(model: HistoryItem, viewBinder: ViewBinder) {
val textView = viewBinder.findViewById<TextView>(R.id.history_item_text)
textView.text = model.text
}
}
22 changes: 22 additions & 0 deletions app/src/main/res/layout/history_item.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:app="http://schemas.android.com/apk/res-auto"

android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
card_view:cardCornerRadius="4dp"
card_view:cardUseCompatPadding="true"
app:contentPadding="10dp"
android:foreground="?android:attr/selectableItemBackground"
android:clickable="true">

<TextView
android:id="@+id/history_item_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp" />

</androidx.cardview.widget.CardView>
49 changes: 34 additions & 15 deletions app/src/main/res/layout/layout_activity_main.xml
Original file line number Diff line number Diff line change
@@ -1,26 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<ImageView
android:id="@+id/image"
android:layout_width="120dp"
android:layout_height="120dp"
android:src="@drawable/app_icon"
android:layout_marginTop="100dp"
android:layout_centerHorizontal="true"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>

<ScrollView
android:id="@+id/prompt_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<ImageView
android:id="@+id/image"
android:layout_width="120dp"
android:layout_height="120dp"
android:src="@drawable/app_icon"
android:layout_marginTop="100dp"
android:layout_centerHorizontal="true"/>

<TextView
android:layout_below="@id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:padding="18dp"
android:text="@string/prompt"
android:textSize="18sp" />
<TextView
android:layout_below="@id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:padding="18dp"
android:text="@string/prompt"
android:textSize="18sp" />
</RelativeLayout>
</ScrollView>

<FrameLayout
android:id="@+id/assistant_container"
Expand Down
8 changes: 4 additions & 4 deletions app/src/main/res/values/styles.xml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<resources>

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
<item name="android:colorPrimary">@color/colorPrimary</item>
<item name="android:colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="android:colorAccent">@color/colorAccent</item>
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>

<!-- Customize Assistant components here -->

Expand Down
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ android.useAndroidX=true
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
kapt.incremental.apt=false

0 comments on commit acff450

Please sign in to comment.