feat(ui): BuddyActivity

parent b4e832a5
......@@ -25,10 +25,12 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
......
......@@ -14,6 +14,24 @@
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".BuddyListActivity"
android:label="@string/title_buddy_list"
android:parentActivityName=".ui.FriendsActivity"
android:theme="@style/AppTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="fr.plnech.dunbar.ui.FriendsActivity" />
</activity>
<activity
android:name=".BuddyDetailActivity"
android:label="@string/title_buddy_detail"
android:parentActivityName=".BuddyListActivity"
android:theme="@style/AppTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="fr.plnech.dunbar.BuddyListActivity" />
</activity>
<activity
android:name=".ui.FriendsActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
......@@ -24,4 +42,5 @@
</intent-filter>
</activity>
</application>
</manifest>
\ No newline at end of file
package fr.plnech.dunbar
import android.content.Intent
import android.os.Bundle
import com.google.android.material.snackbar.Snackbar
import androidx.appcompat.app.AppCompatActivity
import android.view.MenuItem
import kotlinx.android.synthetic.main.activity_buddy_detail.*
/**
* An activity representing a single Buddy detail screen. This
* activity is only used on narrow width devices. On tablet-size devices,
* item details are presented side-by-side with a list of items
* in a [BuddyListActivity].
*/
class BuddyDetailActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_buddy_detail)
setSupportActionBar(detail_toolbar)
fab.setOnClickListener { view ->
Snackbar.make(view, "Replace with your own detail action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
}
// Show the Up button in the action bar.
supportActionBar?.setDisplayHomeAsUpEnabled(true)
// savedInstanceState is non-null when there is fragment state
// saved from previous configurations of this activity
// (e.g. when rotating the screen from portrait to landscape).
// In this case, the fragment will automatically be re-added
// to its container so we don't need to manually add it.
// For more information, see the Fragments API guide at:
//
// http://developer.android.com/guide/components/fragments.html
//
if (savedInstanceState == null) {
// Create the detail fragment and add it to the activity
// using a fragment transaction.
val fragment = BuddyDetailFragment().apply {
arguments = Bundle().apply {
putString(
BuddyDetailFragment.ARG_ITEM_ID,
intent.getStringExtra(BuddyDetailFragment.ARG_ITEM_ID)
)
}
}
supportFragmentManager.beginTransaction()
.add(R.id.buddy_detail_container, fragment)
.commit()
}
}
override fun onOptionsItemSelected(item: MenuItem) =
when (item.itemId) {
android.R.id.home -> {
// This ID represents the Home or Up button. In the case of this
// activity, the Up button is shown. For
// more details, see the Navigation pattern on Android Design:
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
navigateUpTo(Intent(this, BuddyListActivity::class.java))
true
}
else -> super.onOptionsItemSelected(item)
}
}
package fr.plnech.dunbar
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import fr.plnech.dunbar.dummy.DummyContent
import fr.plnech.dunbar.model.Friend
import kotlinx.android.synthetic.main.activity_buddy_detail.*
import kotlinx.android.synthetic.main.buddy_detail.view.*
/**
* A fragment representing a single Buddy detail screen.
* This fragment is either contained in a [BuddyListActivity]
* in two-pane mode (on tablets) or a [BuddyDetailActivity]
* on handsets.
*/
class BuddyDetailFragment : Fragment() {
/**
* The dummy content this fragment is presenting.
*/
private var item: Friend? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
if (it.containsKey(ARG_ITEM_ID)) {
// Load the dummy content specified by the fragment
// arguments. In a real-world scenario, use a Loader
// to load content from a content provider.
item = DummyContent.ITEM_MAP[it.getInt(ARG_ITEM_ID)]
activity?.toolbar_layout?.title = item?.name
}
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val rootView = inflater.inflate(R.layout.buddy_detail, container, false)
// Show the dummy content as text in a TextView.
item?.let {
rootView.buddy_detail.text = it.mapString()
activity?.title = it.name
}
return rootView
}
companion object {
/**
* The fragment argument representing the item ID that this fragment
* represents.
*/
const val ARG_ITEM_ID = "item_id"
}
}
package fr.plnech.dunbar
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.NavUtils
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import fr.plnech.dunbar.dummy.DummyContent
import fr.plnech.dunbar.model.Friend
import kotlinx.android.synthetic.main.activity_buddy_list.*
import kotlinx.android.synthetic.main.buddy_list.*
import kotlinx.android.synthetic.main.buddy_list_content.view.*
class BuddyListActivity : AppCompatActivity() {
/**
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
* device.
*/
private var twoPane: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_buddy_list)
setSupportActionBar(toolbar)
toolbar.title = title
fab.setOnClickListener { view ->
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
}
// Show the Up button in the action bar.
supportActionBar?.setDisplayHomeAsUpEnabled(true)
if (buddy_detail_container != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-w900dp).
// If this view is present, then the
// activity should be in two-pane mode.
twoPane = true
}
setupRecyclerView(buddy_list)
}
override fun onOptionsItemSelected(item: MenuItem) =
when (item.itemId) {
android.R.id.home -> {
NavUtils.navigateUpFromSameTask(this)
true
}
else -> super.onOptionsItemSelected(item)
}
private fun setupRecyclerView(recyclerView: RecyclerView) {
recyclerView.adapter = SimpleItemRecyclerViewAdapter(this, DummyContent.ITEMS, twoPane)
}
class SimpleItemRecyclerViewAdapter(
private val parentActivity: BuddyListActivity,
private val values: List<Friend>,
private val twoPane: Boolean
) :
RecyclerView.Adapter<SimpleItemRecyclerViewAdapter.ViewHolder>() {
private val onClickListener: View.OnClickListener
init {
onClickListener = View.OnClickListener { v ->
val item = v.tag as Friend
if (twoPane) {
val fragment = BuddyDetailFragment().apply {
arguments = Bundle().apply {
putString(BuddyDetailFragment.ARG_ITEM_ID, "friend_${item.id}")
}
}
parentActivity.supportFragmentManager
.beginTransaction()
.replace(R.id.buddy_detail_container, fragment)
.commit()
} else {
val intent = Intent(v.context, BuddyDetailActivity::class.java).apply {
putExtra(BuddyDetailFragment.ARG_ITEM_ID, "friend_${item.id}")
}
v.context.startActivity(intent)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.buddy_list_content, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = values[position]
holder.idView.text = item.name
holder.contentView.text = item.mapString()
with(holder.itemView) {
tag = item
setOnClickListener(onClickListener)
}
}
override fun getItemCount() = values.size
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val idView: TextView = view.id_text
val contentView: TextView = view.content
}
}
}
......@@ -15,7 +15,8 @@ fun String.plural(count: Int = 1): String {
}
}
fun Context.fetchFriends(): List<Friend> {
fun Context.fetchFriends(includeNot: Boolean = true): List<Friend> {
// TODO: https://developer.android.com/training/permissions/requesting
val friends = mutableListOf<Friend>()
//TODO: More efficient query using non-null parameters
......@@ -42,7 +43,10 @@ fun Context.fetchFriends(): List<Friend> {
it.close()
}
return friends.sortedByDescending { it.timesContacted }
println("Fetched ${friends.size} friends.")
return (if (includeNot) friends else friends.filter { it.lastDate != null })
.sortedByDescending { it.timesContacted }
}
......@@ -66,6 +70,5 @@ private fun Context.getFriendPhoto(id: Long): Bitmap? {
} catch (e: IOException) {
e.printStackTrace()
}
println("Photo for $id: $photo")
return photo
}
\ No newline at end of file
package fr.plnech.dunbar.dummy
import android.provider.ContactsContract
import fr.plnech.dunbar.model.Friend
import java.util.*
/**
* Helper class for providing sample content for user interfaces created by
* Android template wizards.
*
* TODO: Replace all uses of this class before publishing your app.
*/
object DummyContent {
/**
* An array of sample (dummy) items.
*/
val ITEMS: MutableList<Friend> = ArrayList()
/**
* A map of sample (dummy) items, by ID.
*/
val ITEM_MAP: MutableMap<Int, Friend> = HashMap()
private val COUNT = 25
init {
// Add some sample items.
for (i in 1..COUNT) {
addItem(createFriend(i))
}
}
private fun addItem(item: Friend) {
ITEMS.add(item)
ITEM_MAP[item.id] = item
}
private fun createFriend(position: Int): Friend {
return Friend(
mapOf(
ContactsContract.Contacts._ID to "$position",
ContactsContract.Contacts.DISPLAY_NAME to "Friend $position",
"data1" to "01 23 45 67 89",
"details" to "$position: this friend has a lot of details about them. What do you think?"
)
)
}
}
......@@ -6,7 +6,7 @@ import android.net.Uri
import android.provider.ContactsContract.Contacts
import java.util.*
data class Friend(val map: MutableMap<String, String?>, val photo: Bitmap?) {
data class Friend(val map: Map<String, String?> = mutableMapOf(), val photo: Bitmap? = null) {
override fun toString(): String = "$name"
fun mapString(): String = map.entries.filter { !it.value.isNullOrEmpty() }.toString()
......@@ -23,11 +23,12 @@ data class Friend(val map: MutableMap<String, String?>, val photo: Bitmap?) {
val phone: String?
get() = if (map[Contacts.HAS_PHONE_NUMBER] == "1") map["data1"] else null
val lastTimeStamp = map[Contacts.LAST_TIME_CONTACTED]!!.toLong()
val lastTimeStamp = map[Contacts.LAST_TIME_CONTACTED]?.toLong() ?: 0
val lastDate: Date?
get() = if (lastTimeStamp > 0) Date(lastTimeStamp) else null
//FIXME: Broken on AndroidQ. Use messages / phone log
val timesContacted: Int
get() = map[Contacts.TIMES_CONTACTED]?.toInt() ?: 0
......@@ -51,7 +52,5 @@ data class Friend(val map: MutableMap<String, String?>, val photo: Bitmap?) {
Intent(Intent.ACTION_DIAL, Uri.parse("tel:${it}"))
}
}
}
package fr.plnech.dunbar.ui
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
......@@ -7,6 +8,7 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.NotificationManagerCompat
import androidx.recyclerview.widget.LinearLayoutManager
import fr.plnech.dunbar.BuddyListActivity
import fr.plnech.dunbar.R
import fr.plnech.dunbar.data.Messages
import fr.plnech.dunbar.fetchFriends
......@@ -74,6 +76,10 @@ class FriendsActivity : AppCompatActivity() {
// as you specify a parent activity in AndroidManifest.xml.
return when (item.itemId) {
R.id.action_settings -> true
R.id.action_buddies -> {
startActivity(Intent(this, BuddyListActivity::class.java))
true
}
else -> super.onOptionsItemSelected(item)
}
}
......
......@@ -69,9 +69,7 @@ class FriendsViewHolder(private val view: View) : RecyclerView.ViewHolder(view)
}
private fun bindPic(friend: Friend) {
friend.photo?.let {
pic.setImageBitmap(it)
}
pic.setImageBitmap(friend.photo)
}
private fun bindPhone(friend: Friend) {
......
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:baselineAligned="false"
android:divider="?android:attr/dividerHorizontal"
android:orientation="horizontal"
android:showDividers="middle"
tools:context=".BuddyListActivity">
<!--
This layout is a two-pane layout for the Buddies
master/detail flow.
-->
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/buddy_list"
android:name="fr.plnech.dunbar.BuddyListFragment"
android:layout_width="@dimen/item_width"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"
tools:context="fr.plnech.dunbar.BuddyListActivity"
tools:listitem="@layout/buddy_list_content" />
<FrameLayout
android:id="@+id/buddy_detail_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".BuddyDetailActivity"
tools:ignore="MergeRootFrame">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/app_bar_height"
android:fitsSystemWindows="true"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:toolbarId="@+id/toolbar">
<androidx.appcompat.widget.Toolbar
android:id="@+id/detail_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/buddy_detail_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
android:layout_margin="@dimen/fab_margin"
app:layout_anchor="@+id/buddy_detail_container"
app:layout_anchorGravity="top|end"
app:srcCompat="@android:drawable/stat_notify_chat" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".BuddyListActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
android:id="@+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<include layout="@layout/buddy_list" />
</FrameLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@android:drawable/ic_dialog_email" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/buddy_detail"
style="?android:attr/textAppearanceLarge"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
android:textIsSelectable="true"
tools:context=".BuddyDetailFragment" />
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/buddy_list"
android:name="fr.plnech.dunbar.BuddyListFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"
tools:context=".BuddyListActivity"
tools:listitem="@layout/buddy_list_content" />
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/id_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:textAppearance="?attr/textAppearanceListItem" />
<TextView
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:textAppearance="?attr/textAppearanceListItem" />
</LinearLayout>
\ No newline at end of file
......@@ -26,6 +26,7 @@
android:id="@+id/friendsList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
......
......@@ -3,6 +3,11 @@
xmlns:tools="http://schemas.android.com/tools"
tools:context="fr.plnech.dunbar.ui.FriendsActivity">
<item
android:id="@+id/action_buddies"
android:orderInCategory="100"
android:title="@string/action_buddies"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:title="@string/action_settings"
......
<resources>
<dimen name="fab_margin">16dp</dimen>
<dimen name="app_bar_height">200dp</dimen>
<dimen name="item_width">200dp</dimen>
<dimen name="text_margin">16dp</dimen>
</resources>
......@@ -3,4 +3,7 @@
<string name="channel_name">Dunbar</string>
<string name="channel_description">Updates about friends you don\'t want to forget</string>
<string name="action_settings">Settings</string>
<string name="action_buddies">Buddies</string>
<string name="title_buddy_list">Buddies</string>
<string name="title_buddy_detail">Buddy Detail</string>
</resources>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment