From b2f7fdbb3b7c592cec345db23d34354260636a45 Mon Sep 17 00:00:00 2001 From: Paul-Louis NECH Date: Sun, 10 Nov 2019 12:47:57 +0100 Subject: [PATCH] feat(ui): BuddyActivity --- app/build.gradle | 4 +++- app/src/main/AndroidManifest.xml | 19 +++++++++++++++++++ app/src/main/java/fr/plnech/dunbar/BuddyDetailActivity.kt | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ app/src/main/java/fr/plnech/dunbar/BuddyDetailFragment.kt | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ app/src/main/java/fr/plnech/dunbar/BuddyListActivity.kt | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ app/src/main/java/fr/plnech/dunbar/Extensions.kt | 9 ++++++--- app/src/main/java/fr/plnech/dunbar/dummy/DummyContent.kt | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ app/src/main/java/fr/plnech/dunbar/model/Friend.kt | 7 +++---- app/src/main/java/fr/plnech/dunbar/ui/FriendsActivity.kt | 6 ++++++ app/src/main/java/fr/plnech/dunbar/ui/FriendsAdapter.kt | 4 +--- app/src/main/res/drawable/dunbar.jpg | Bin 0 -> 179440 bytes app/src/main/res/layout-w900dp/buddy_list.xml | 39 +++++++++++++++++++++++++++++++++++++++ app/src/main/res/layout/activity_buddy_detail.xml | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ app/src/main/res/layout/activity_buddy_list.xml | 42 ++++++++++++++++++++++++++++++++++++++++++ app/src/main/res/layout/buddy_detail.xml | 10 ++++++++++ app/src/main/res/layout/buddy_list.xml | 13 +++++++++++++ app/src/main/res/layout/buddy_list_content.xml | 20 ++++++++++++++++++++ app/src/main/res/layout/content_friends.xml | 1 + app/src/main/res/menu/menu_main.xml | 5 +++++ app/src/main/res/values/dimens.xml | 3 +++ app/src/main/res/values/strings.xml | 3 +++ 21 files changed, 532 insertions(+), 11 deletions(-) create mode 100644 app/src/main/java/fr/plnech/dunbar/BuddyDetailActivity.kt create mode 100644 app/src/main/java/fr/plnech/dunbar/BuddyDetailFragment.kt create mode 100644 app/src/main/java/fr/plnech/dunbar/BuddyListActivity.kt create mode 100644 app/src/main/java/fr/plnech/dunbar/dummy/DummyContent.kt create mode 100644 app/src/main/res/drawable/dunbar.jpg create mode 100644 app/src/main/res/layout-w900dp/buddy_list.xml create mode 100644 app/src/main/res/layout/activity_buddy_detail.xml create mode 100644 app/src/main/res/layout/activity_buddy_list.xml create mode 100644 app/src/main/res/layout/buddy_detail.xml create mode 100644 app/src/main/res/layout/buddy_list.xml create mode 100644 app/src/main/res/layout/buddy_list_content.xml diff --git a/app/build.gradle b/app/build.gradle index 96cc465..a63949c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e4c0ecf..2332e1e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,6 +14,24 @@ android:supportsRtl="true" android:theme="@style/AppTheme"> + + + + + + @@ -24,4 +42,5 @@ + \ No newline at end of file diff --git a/app/src/main/java/fr/plnech/dunbar/BuddyDetailActivity.kt b/app/src/main/java/fr/plnech/dunbar/BuddyDetailActivity.kt new file mode 100644 index 0000000..f2b9603 --- /dev/null +++ b/app/src/main/java/fr/plnech/dunbar/BuddyDetailActivity.kt @@ -0,0 +1,72 @@ +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) + } +} diff --git a/app/src/main/java/fr/plnech/dunbar/BuddyDetailFragment.kt b/app/src/main/java/fr/plnech/dunbar/BuddyDetailFragment.kt new file mode 100644 index 0000000..d7144c4 --- /dev/null +++ b/app/src/main/java/fr/plnech/dunbar/BuddyDetailFragment.kt @@ -0,0 +1,62 @@ +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" + } +} diff --git a/app/src/main/java/fr/plnech/dunbar/BuddyListActivity.kt b/app/src/main/java/fr/plnech/dunbar/BuddyListActivity.kt new file mode 100644 index 0000000..975d13f --- /dev/null +++ b/app/src/main/java/fr/plnech/dunbar/BuddyListActivity.kt @@ -0,0 +1,121 @@ +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, + private val twoPane: Boolean + ) : + RecyclerView.Adapter() { + + 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 + } + } +} diff --git a/app/src/main/java/fr/plnech/dunbar/Extensions.kt b/app/src/main/java/fr/plnech/dunbar/Extensions.kt index 0d3f6b5..35ae3eb 100644 --- a/app/src/main/java/fr/plnech/dunbar/Extensions.kt +++ b/app/src/main/java/fr/plnech/dunbar/Extensions.kt @@ -15,7 +15,8 @@ fun String.plural(count: Int = 1): String { } } -fun Context.fetchFriends(): List { +fun Context.fetchFriends(includeNot: Boolean = true): List { + // TODO: https://developer.android.com/training/permissions/requesting val friends = mutableListOf() //TODO: More efficient query using non-null parameters @@ -42,7 +43,10 @@ fun Context.fetchFriends(): List { 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 diff --git a/app/src/main/java/fr/plnech/dunbar/dummy/DummyContent.kt b/app/src/main/java/fr/plnech/dunbar/dummy/DummyContent.kt new file mode 100644 index 0000000..72503c6 --- /dev/null +++ b/app/src/main/java/fr/plnech/dunbar/dummy/DummyContent.kt @@ -0,0 +1,49 @@ +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 = ArrayList() + + /** + * A map of sample (dummy) items, by ID. + */ + val ITEM_MAP: MutableMap = 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?" + ) + ) + } +} diff --git a/app/src/main/java/fr/plnech/dunbar/model/Friend.kt b/app/src/main/java/fr/plnech/dunbar/model/Friend.kt index 4adabf4..21d9389 100644 --- a/app/src/main/java/fr/plnech/dunbar/model/Friend.kt +++ b/app/src/main/java/fr/plnech/dunbar/model/Friend.kt @@ -6,7 +6,7 @@ import android.net.Uri import android.provider.ContactsContract.Contacts import java.util.* -data class Friend(val map: MutableMap, val photo: Bitmap?) { +data class Friend(val map: Map = 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, 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, val photo: Bitmap?) { Intent(Intent.ACTION_DIAL, Uri.parse("tel:${it}")) } } - - } diff --git a/app/src/main/java/fr/plnech/dunbar/ui/FriendsActivity.kt b/app/src/main/java/fr/plnech/dunbar/ui/FriendsActivity.kt index a4b1f70..4717ba7 100644 --- a/app/src/main/java/fr/plnech/dunbar/ui/FriendsActivity.kt +++ b/app/src/main/java/fr/plnech/dunbar/ui/FriendsActivity.kt @@ -1,5 +1,6 @@ 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) } } diff --git a/app/src/main/java/fr/plnech/dunbar/ui/FriendsAdapter.kt b/app/src/main/java/fr/plnech/dunbar/ui/FriendsAdapter.kt index 456bd9e..b78dd63 100644 --- a/app/src/main/java/fr/plnech/dunbar/ui/FriendsAdapter.kt +++ b/app/src/main/java/fr/plnech/dunbar/ui/FriendsAdapter.kt @@ -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) { diff --git a/app/src/main/res/drawable/dunbar.jpg b/app/src/main/res/drawable/dunbar.jpg new file mode 100644 index 0000000..3bfab2c Binary files /dev/null and b/app/src/main/res/drawable/dunbar.jpg differ diff --git a/app/src/main/res/layout-w900dp/buddy_list.xml b/app/src/main/res/layout-w900dp/buddy_list.xml new file mode 100644 index 0000000..1a453ac --- /dev/null +++ b/app/src/main/res/layout-w900dp/buddy_list.xml @@ -0,0 +1,39 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_buddy_detail.xml b/app/src/main/res/layout/activity_buddy_detail.xml new file mode 100644 index 0000000..4cdf42d --- /dev/null +++ b/app/src/main/res/layout/activity_buddy_detail.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_buddy_list.xml b/app/src/main/res/layout/activity_buddy_list.xml new file mode 100644 index 0000000..43cf072 --- /dev/null +++ b/app/src/main/res/layout/activity_buddy_list.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/buddy_detail.xml b/app/src/main/res/layout/buddy_detail.xml new file mode 100644 index 0000000..ef9a0c2 --- /dev/null +++ b/app/src/main/res/layout/buddy_detail.xml @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/buddy_list.xml b/app/src/main/res/layout/buddy_list.xml new file mode 100644 index 0000000..1a28090 --- /dev/null +++ b/app/src/main/res/layout/buddy_list.xml @@ -0,0 +1,13 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/buddy_list_content.xml b/app/src/main/res/layout/buddy_list_content.xml new file mode 100644 index 0000000..395ec3e --- /dev/null +++ b/app/src/main/res/layout/buddy_list_content.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/content_friends.xml b/app/src/main/res/layout/content_friends.xml index 93351a0..ada0014 100644 --- a/app/src/main/res/layout/content_friends.xml +++ b/app/src/main/res/layout/content_friends.xml @@ -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" diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml index 3ca9b6b..8ef05c0 100644 --- a/app/src/main/res/menu/menu_main.xml +++ b/app/src/main/res/menu/menu_main.xml @@ -3,6 +3,11 @@ xmlns:tools="http://schemas.android.com/tools" tools:context="fr.plnech.dunbar.ui.FriendsActivity"> + 16dp + 200dp + 200dp + 16dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 932fcfb..398d808 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3,4 +3,7 @@ Dunbar Updates about friends you don\'t want to forget Settings + Buddies + Buddies + Buddy Detail -- libgit2 0.27.0