Commenting & seeing comments on a post ( issues: #37 #77 ) (#87)

Comment and like buttons added + a few UI tweaks 

* WIP posts

* WIP posts

* trying to add images

* trying to add images

* Got posts working and linked them to the profile

* added tests for Post

* layout changes

* moved a test file

* refactoring

* refactoring

* removed wrong annotation in unit test

* removed an import that was breaking the build

* removed tests that broke from merge, will override with master

* added UI test for the post activity

* WIP posts

* WIP posts

* trying to add images

* trying to add images

* Got posts working and linked them to the profile

* layout changes

* refactoring

* refactoring

* WIP posts

* WIP posts

* trying to add images

* trying to add images

* Got posts working and linked them to the profile

* added tests for Post

* layout changes

* moved a test file

* refactoring

* refactoring

* removed wrong annotation in unit test

* removed an import that was breaking the build

* removed tests that broke from merge, will override with master

* fixed merging errors

* trying my best to merge

* removed drawable definition in activity_post.xml

* Started converting Post to a fragment

* got a working feed

* WI

* removed non-valid test

* rebase on other branch

* moved the feed to the home page

* Add tests

* delete test for now

* Adapt test to changes (no more profile from drawer)

* Add unit test for api

* Add test for profile, refactor to allow testing, add exception to security policy to allow tests

* Adapt test to new situation

* Fix typo due to change

* refactor somewhat

* added a feed test

* WIP posts

* trying to add images

* WIP posts

* trying to add images

* Got posts working and linked them to the profile

* added tests for Post

* layout changes

* moved a test file

* refactoring

* refactoring

* WIP posts

* WIP posts

* trying to add images

* trying to add images

* Got posts working and linked them to the profile

* added tests for Post

* layout changes

* moved a test file

* refactoring

* refactoring

* removed wrong annotation in unit test

* removed an import that was breaking the build

* removed tests that broke from merge, will override with master

* added UI test for the post activity

* WIP posts

* trying to add images

* WIP posts

* trying to add images

* Got posts working and linked them to the profile

* added tests for Post

* layout changes

* moved a test file

* refactoring

* refactoring

* WIP posts

* WIP posts

* trying to add images

* trying to add images

* Got posts working and linked them to the profile

* added tests for Post

* layout changes

* moved a test file

* refactoring

* refactoring

* removed wrong annotation in unit test

* removed an import that was breaking the build

* removed tests that broke from merge, will override with master

* added UI test for the post activity

* fixed merging errors

* trying my best to merge

* removed drawable definition in activity_post.xml

* Started converting Post to a fragment

* got a working feed

* WI

* removed non-valid test

* WIP posts

* WIP posts

* trying to add images

* trying to add images

* Got posts working and linked them to the profile

* added tests for Post

* layout changes

* moved a test file

* WIP posts

* WIP posts

* trying to add images

* trying to add images

* Got posts working and linked them to the profile

* added tests for Post

* layout changes

* moved a test file

* refactoring

* refactoring

* refactoring

* refactoring

* removed wrong annotation in unit test

* WIP posts

* WIP posts

* WIP posts

* WIP posts

* trying to add images

* trying to add images

* trying to add images

* trying to add images

* Got posts working and linked them to the profile

* Got posts working and linked them to the profile

* added tests for Post

* layout changes

* layout changes

* moved a test file

* refactoring

* refactoring

* refactoring

* refactoring

* removed wrong annotation in unit test

* removed an import that was breaking the build

* removed an import that was breaking the build

* removed tests that broke from merge, will override with master

* removed tests that broke from merge, will override with master

* added UI test for the post activity

* fixed merging errors

* trying my best to merge

* removed drawable definition in activity_post.xml

* Started converting Post to a fragment

* got a working feed

* WI

* removed non-valid test

* rebase on other branch

* moved the feed to the home page

* added a feed test

* added a working feed test

* fixed broken test

* merged with master

* added a max height for images and made profile pictures round

* Added a default image for the post

* created a PostActivity to look a single posts

* fixed buggy postActivity

* Complete overhall of the feed UI

* removed test that didn't please Travis

* removed legacy test

* changed feedAdapter init location (outside of network callback)

* changed the feed from public timeline to home timeline

* Refactored myProfile page

* Converted profile picture to round image

* restored feed test

* I can like a post, but unlike is still a WIP

* Liking kind of works now and added tests

* fixed an error, now we can unlike as well

* fixed travis constraint error

* Display user's posts on profile page

* moved test to Mock server tests

* fixed test

* last resort debugging

* Changed fixed size of profile posts

* last resort debugging

* last resort debugging

* last resort debugging

* made post_activity profilepic round

* Total refactor of profile posts

* still have a weird bug with the comments: input is always null (WIP)

* still trying to fix coments

* removed annoying side margins in the home feed

* trying to fix comments

* fixed null comment

* converted all posts back to statuses and got rid of post

* Refactored recycler view

* Merged with my-profile

* Posts displayed on profile page

* Added links to profile activity where needed

* fixed comment posting

* finished implementing comments, but api is buggy so none are visible

* removed useless space in profile page

* fixed ci config bug

* trying to trigger ci hook (github was down last time)

* updated tests with master tests

* added tests for the comments

* added tests for the comments

* added first() matcher to fix comment test

* still trying to fix comment tests' null progress bar

* getting rid of that null progress bar

* added comment test

* fixed merge error

* added like button test

* added more post tests

* took pr coments into account

* added back an old test

* added mockServer response for comment test and fixed comment null pointer bug

* changed notification UI to better separate notifications

* added mockserver response for likes and corrected like toggling error

* added a test for posting comments

* fixed typo in test

* a gift for code climate

* refactored stuff

* fixed broken imports

* comment refactored as xml

Co-authored-by: Matthieu <61561059+Wv5twkFEKh54vo4tta9yu7dHa3@users.noreply.github.com>
Co-authored-by: mjaillot <marie.jaillot@epfl.ch>
This commit is contained in:
Andrew Dobis 2020-04-11 12:55:06 +02:00 committed by GitHub
parent 8af19cbd9a
commit f8d6c67079
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 956 additions and 243 deletions

View File

@ -2,7 +2,6 @@
# Originally written by Ralf Kistner <ralf@embarkmobile.com>, but placed in the public domain
#set -x
set +e
bootanim=""

View File

@ -77,7 +77,7 @@ dependencies {
}
implementation "com.github.bumptech.glide:okhttp-integration:4.11.0"
implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") {
implementation("com.github.bumptech.glide:recyclerview-integration:4.11.0") {
// Excludes the support library because it's already included by Glide.
transitive = false
}
@ -95,7 +95,7 @@ dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.2.0'
androidTestImplementation 'com.android.support.test.espresso:espresso-contrib:3.0.2'
implementation 'androidx.paging:paging-runtime-ktx:2.1.1'
implementation 'androidx.paging:paging-runtime-ktx:2.1.2'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
}

View File

@ -1,22 +1,34 @@
package com.h.pixeldroid
import android.R.attr.x
import android.content.Context
import android.text.Editable
import android.view.Gravity
import android.view.View
import android.widget.EditText
import android.widget.TextView
import androidx.core.text.set
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.action.*
import androidx.test.espresso.action.ViewActions.swipeDown
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.DrawerActions
import androidx.test.espresso.contrib.DrawerMatchers
import androidx.test.espresso.contrib.NavigationViewActions
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.google.android.material.tabs.TabLayout
import com.h.pixeldroid.fragments.feeds.HomeFragment
import com.h.pixeldroid.fragments.feeds.ViewHolder
import com.h.pixeldroid.testUtility.MockServer
import org.hamcrest.BaseMatcher
import org.hamcrest.Matcher
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@ -26,9 +38,88 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class MockedServerTest {
private fun <T> first(matcher: Matcher<T>): Matcher<T>? {
return object : BaseMatcher<T>() {
var isFirst = true
override fun describeTo(description: org.hamcrest.Description?) {
description?.appendText("first matching item")
}
override fun matches(item: Any?): Boolean {
if (isFirst && matcher.matches(item)) {
isFirst = false
return true
}
return false
}
}
}
/**
* @param percent can be 1 or 0
* 1: swipes all the way up
* 0: swipes half way up
*/
private fun slowSwipeUp(percent: Boolean) : ViewAction {
return ViewActions.actionWithAssertions(
GeneralSwipeAction(
Swipe.SLOW,
GeneralLocation.BOTTOM_CENTER,
if(percent) GeneralLocation.TOP_CENTER else GeneralLocation.CENTER,
Press.FINGER)
)
}
fun getText(matcher: Matcher<View?>?): String? {
val stringHolder = arrayOf<String?>(null)
onView(matcher).perform(object : ViewAction {
override fun getConstraints(): Matcher<View> {
return isAssignableFrom(TextView::class.java)
}
override fun getDescription(): String {
return "getting text from a TextView"
}
override fun perform(
uiController: UiController,
view: View
) {
val tv = view as TextView //Save, because of check in getConstraints()
stringHolder[0] = tv.text.toString()
}
})
return stringHolder[0]
}
private fun clickChildViewWithId(id: Int) = object : ViewAction {
override fun getConstraints() = null
override fun getDescription() = "click child view with id $id"
override fun perform(uiController: UiController, view: View) {
val v = view.findViewById<View>(id)
v.performClick()
}
}
private fun typeTextInViewWithId(id: Int, text: String) = object : ViewAction {
override fun getConstraints() = null
override fun getDescription() = "click child view with id $id"
override fun perform(uiController: UiController, view: View) {
val v = view.findViewById<EditText>(id)
v.text.append(text)
}
}
val mockServer = MockServer()
@get:Rule
var globalTimeout: Timeout = Timeout.seconds(100)
@get:Rule
@ -122,13 +213,13 @@ class MockedServerTest {
// Open Drawer to click on navigation.
onView(withId(R.id.drawer_layout))
.check(matches(DrawerMatchers.isClosed(Gravity.LEFT))) // Left Drawer should be closed.
.perform(DrawerActions.open()); // Open Drawer
.perform(DrawerActions.open()) // Open Drawer
// Start the screen of your activity.
onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_settings))
// Check that settings activity was opened.
onView(withText(R.string.signature_title)).check(matches(ViewMatchers.isDisplayed()))
onView(withText(R.string.signature_title)).check(matches(isDisplayed()))
}
@Test
@ -139,7 +230,7 @@ class MockedServerTest {
.perform(ViewActions.swipeLeft()) // notifications
.perform(ViewActions.swipeLeft()) // profile
.perform(ViewActions.swipeLeft()) // should stop at profile
onView(withId(R.id.nbFollowersTextView)).check(matches(ViewMatchers.isDisplayed()))
onView(withId(R.id.nbFollowersTextView)).check(matches(isDisplayed()))
}
@Test
@ -153,6 +244,116 @@ class MockedServerTest {
.perform(ViewActions.swipeRight()) // search
.perform(ViewActions.swipeRight()) // homepage
.perform(ViewActions.swipeRight()) // should stop at homepage
onView(withId(R.id.list)).check(matches(ViewMatchers.isDisplayed()))
onView(withId(R.id.list)).check(matches(isDisplayed()))
}
@Test
fun clickingLikeButtonWorks() {
ActivityScenario.launch(MainActivity::class.java)
Thread.sleep(1000)
//Get initial like count
val likes = getText(withId(R.id.nlikes))
//Like the post
onView(withId(R.id.list))
.perform(actionOnItemAtPosition<ViewHolder>
(0, clickChildViewWithId(R.id.liker)))
Thread.sleep(100)
//Unlike the post
onView(withId(R.id.list))
.perform(actionOnItemAtPosition<ViewHolder>
(0, clickChildViewWithId(R.id.liker)))
//...
Thread.sleep(100)
//Profit
onView(withId(R.id.nlikes)).check(matches((withText(likes))))
}
@Test
fun clickingUsernameOpensProfile() {
ActivityScenario.launch(MainActivity::class.java)
Thread.sleep(1000)
//Get initial like count
onView(withId(R.id.list))
.perform(actionOnItemAtPosition<ViewHolder>
(0, clickChildViewWithId(R.id.username)))
Thread.sleep(1000)
//Check that the Profile opened
onView(withId(R.id.accountNameTextView)).check(matches(isDisplayed()))
}
@Test
fun clickingProfilePicOpensProfile() {
ActivityScenario.launch(MainActivity::class.java)
Thread.sleep(1000)
//Get initial like count
onView(withId(R.id.list))
.perform(actionOnItemAtPosition<ViewHolder>
(0, clickChildViewWithId(R.id.profilePic)))
Thread.sleep(1000)
//Check that the Profile opened
onView(withId(R.id.accountNameTextView)).check(matches(isDisplayed()))
}
@Test
fun clickingCommentButtonOpensCommentSection() {
ActivityScenario.launch(MainActivity::class.java)
Thread.sleep(1000)
//Click comment button and then try to see if the commenter exists
onView(withId(R.id.list))
.perform(actionOnItemAtPosition<ViewHolder>
(0, clickChildViewWithId(R.id.commenter)))
Thread.sleep(1000)
onView(withId(R.id.commentIn))
.check(matches(hasDescendant(withId(R.id.editComment))))
}
@Test
fun clickingViewCommentShowsTheComments() {
ActivityScenario.launch(MainActivity::class.java)
Thread.sleep(1000)
//Open the comment section
onView(withId(R.id.list))
.perform(actionOnItemAtPosition<ViewHolder>
(0, clickChildViewWithId(R.id.ViewComments)))
Thread.sleep(1000)
onView(withId(R.id.commentContainer))
.check(matches(hasDescendant(withId(R.id.comment))))
}
@Test
fun postingACommentWorks() {
ActivityScenario.launch(MainActivity::class.java)
Thread.sleep(1000)
//Open the comment section
onView(withId(R.id.list))
.perform(actionOnItemAtPosition<ViewHolder>
(0, clickChildViewWithId(R.id.commenter)))
onView(withId(R.id.list)).perform(slowSwipeUp(true))
onView(withId(R.id.list)).perform(slowSwipeUp(false))
onView(withId(R.id.list)).perform(slowSwipeUp(false))
Thread.sleep(1000)
onView(withId(R.id.list))
.perform(actionOnItemAtPosition<ViewHolder>
(0, typeTextInViewWithId(R.id.editComment, "test")))
onView(withId(R.id.list))
.perform(actionOnItemAtPosition<ViewHolder>
(0, clickChildViewWithId(R.id.submitComment)))
Thread.sleep(1000)
onView(withId(R.id.commentContainer))
.check(matches(hasDescendant(withId(R.id.comment))))
}
}

File diff suppressed because one or more lines are too long

View File

@ -3,8 +3,10 @@ package com.h.pixeldroid
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.h.pixeldroid.fragments.PostFragment
import com.h.pixeldroid.objects.Status.Companion.POST_TAG
import com.h.pixeldroid.objects.Status
import com.h.pixeldroid.objects.Status.Companion.POST_TAG
class PostActivity : AppCompatActivity() {
lateinit var postFragment : PostFragment

View File

@ -60,7 +60,7 @@ interface PixelfedAPI {
//Used in our case to post a comment
@FormUrlEncoded
@POST("/api/v1/statuses")
fun commentStatus(
fun postStatus(
//The authorization header needs to be of the form "Bearer <token>"
@Header("Authorization") authorization: String,
@Field("status") statusText : String,

View File

@ -1,5 +1,6 @@
package com.h.pixeldroid.fragments
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
@ -8,9 +9,14 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.bumptech.glide.Glide
import com.h.pixeldroid.BuildConfig
import com.h.pixeldroid.R
import com.h.pixeldroid.objects.Status.Companion.POST_TAG
import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.fragments.feeds.HomeFragment
import com.h.pixeldroid.fragments.feeds.ViewHolder
import com.h.pixeldroid.objects.Status
import com.h.pixeldroid.objects.Status.Companion.POST_TAG
import kotlinx.android.synthetic.main.post_fragment.view.*
@ -27,6 +33,20 @@ class PostFragment : Fragment() {
.placeholder(ColorDrawable(Color.GRAY))
status?.setupPost(root, picRequest, root.postPicture, root.profilePic)
//Setup arguments needed for the onclicklisteners
val holder = ViewHolder(root, context!!)
val preferences = requireActivity().getSharedPreferences(
"${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE
)
val accessToken = preferences.getString("accessToken", "")
val api = PixelfedAPI.create("${preferences.getString("domain", "")}")
//Activate onclickListeners
status?.activateLiker(holder, api, "Bearer $accessToken")
status?.activateCommenter(holder, api, "Bearer $accessToken")
status?.showComments(holder, api, "Bearer $accessToken")
return root
}
}

View File

@ -14,6 +14,7 @@ import com.h.pixeldroid.BuildConfig
import com.h.pixeldroid.R
import com.h.pixeldroid.api.PixelfedAPI
/**
* A fragment representing a list of Items.
* Activities containing this fragment MUST implement the

View File

@ -41,7 +41,7 @@ open class FeedFragment<T: FeedContent, VH: RecyclerView.ViewHolder?>: Fragment(
protected lateinit var list : RecyclerView
protected lateinit var adapter : FeedsRecyclerViewAdapter<T, VH>
private lateinit var swipeRefreshLayout: SwipeRefreshLayout
protected lateinit var swipeRefreshLayout: SwipeRefreshLayout
private lateinit var loadingIndicator: ProgressBar
override fun onCreateView(
@ -51,11 +51,16 @@ open class FeedFragment<T: FeedContent, VH: RecyclerView.ViewHolder?>: Fragment(
): View? {
val view = inflater.inflate(R.layout.fragment_feed, container, false)
//Initialize lateinit fields that are needed as soon as the view is created
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshLayout)
loadingIndicator = view.findViewById(R.id.progressBar)
list = swipeRefreshLayout.list
// Set the adapter
preferences = requireActivity().getSharedPreferences(
"${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE
)
list.layoutManager = LinearLayoutManager(context)
pixelfedAPI = PixelfedAPI.create("${preferences.getString("domain", "")}")
accessToken = preferences.getString("accessToken", "")
return view
}
@ -63,13 +68,6 @@ open class FeedFragment<T: FeedContent, VH: RecyclerView.ViewHolder?>: Fragment(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
preferences = requireActivity().getSharedPreferences(
"${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE
)
pixelfedAPI = PixelfedAPI.create("${preferences.getString("domain", "")}")
accessToken = preferences.getString("accessToken", "")
swipeRefreshLayout.setOnRefreshListener {
//by invalidating data, loadInitial will be called again
factory.liveData.value!!.invalidate()
@ -77,6 +75,7 @@ open class FeedFragment<T: FeedContent, VH: RecyclerView.ViewHolder?>: Fragment(
}
inner class FeedDataSource(private val makeInitialCall: (Int) -> Call<List<T>>,
private val makeAfterCall: (Int, String) -> Call<List<T>>
): ItemKeyedDataSource<String, T>() {
@ -104,11 +103,13 @@ open class FeedFragment<T: FeedContent, VH: RecyclerView.ViewHolder?>: Fragment(
}
private fun enqueueCall(call: Call<List<T>>, callback: LoadCallback<T>){
call.enqueue(object : Callback<List<T>> {
override fun onResponse(call: Call<List<T>>, response: Response<List<T>>) {
if (response.code() == 200) {
val notifications = response.body()!! as ArrayList<T>
callback.onResult(notifications as List<T>)
} else{
Toast.makeText(context,"Something went wrong while loading", Toast.LENGTH_SHORT).show()
}

View File

@ -7,8 +7,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import android.widget.*
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.paging.LivePagedListBuilder
@ -21,12 +20,10 @@ import com.bumptech.glide.integration.recyclerview.RecyclerViewPreloader
import com.bumptech.glide.util.ViewPreloadSizeProvider
import com.h.pixeldroid.R
import com.h.pixeldroid.objects.Status
import com.h.pixeldroid.utils.ImageConverter
import kotlinx.android.synthetic.main.fragment_home.*
import retrofit2.Call
class HomeFragment : FeedFragment<Status, HomeFragment.HomeRecyclerViewAdapter.ViewHolder>() {
class HomeFragment : FeedFragment<Status, ViewHolder>() {
lateinit var picRequest: RequestBuilder<Drawable>
@ -36,8 +33,6 @@ class HomeFragment : FeedFragment<Status, HomeFragment.HomeRecyclerViewAdapter.V
): View? {
val view = super.onCreateView(inflater, container, savedInstanceState)
content = makeContent()
//RequestBuilder that is re-used for every image
picRequest = Glide.with(this)
.asDrawable().fitCenter()
@ -46,12 +41,6 @@ class HomeFragment : FeedFragment<Status, HomeFragment.HomeRecyclerViewAdapter.V
adapter = HomeRecyclerViewAdapter()
list.adapter = adapter
content.observe(viewLifecycleOwner,
Observer { c ->
adapter.submitList(c)
//after a refresh is done we need to stop the pull to refresh spinner
swipeRefreshLayout.isRefreshing = false
})
//Make Glide be aware of the recyclerview and pre-load images
val sizeProvider: ListPreloader.PreloadSizeProvider<Status> = ViewPreloadSizeProvider()
@ -63,6 +52,17 @@ class HomeFragment : FeedFragment<Status, HomeFragment.HomeRecyclerViewAdapter.V
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
content = makeContent()
content.observe(viewLifecycleOwner,
Observer { c ->
adapter.submitList(c)
//after a refresh is done we need to stop the pull to refresh spinner
swipeRefreshLayout.isRefreshing = false
})
}
private fun makeContent(): LiveData<PagedList<Status>> {
fun makeInitialCall(requestedLoadSize: Int): Call<List<Status>> {
return pixelfedAPI
@ -81,13 +81,15 @@ class HomeFragment : FeedFragment<Status, HomeFragment.HomeRecyclerViewAdapter.V
/**
* [RecyclerView.Adapter] that can display a list of Statuses
*/
inner class HomeRecyclerViewAdapter: FeedsRecyclerViewAdapter<Status, HomeRecyclerViewAdapter.ViewHolder>() {
inner class HomeRecyclerViewAdapter()
: FeedsRecyclerViewAdapter<Status, ViewHolder>() {
private val api = pixelfedAPI
private val credential = "Bearer $accessToken"
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.post_fragment, parent, false)
context = view.context
return ViewHolder(view)
return ViewHolder(view, context)
}
/**
@ -100,26 +102,20 @@ class HomeFragment : FeedFragment<Status, HomeFragment.HomeRecyclerViewAdapter.V
holder.profilePic.maxHeight = metrics.heightPixels
holder.postPic.maxHeight = metrics.heightPixels
//Set up the the post
//Setup the post layout
post.setupPost(holder.postView, picRequest, holder.postPic, holder.profilePic)
//Set the image back to a placeholder if the original is too big
if(holder.postPic.height > metrics.heightPixels) {
ImageConverter.setDefaultImage(holder.postView, holder.postPic)
}
}
//Set initial favorite toggle value
holder.isLiked = post.favourited
/**
* Represents the posts that will be contained within the feed
*/
inner class ViewHolder(val postView: View) : RecyclerView.ViewHolder(postView) {
val profilePic : ImageView = postView.findViewById(R.id.profilePic)
val postPic : ImageView = postView.findViewById(R.id.postPicture)
val username : TextView = postView.findViewById(R.id.username)
val usernameDesc: TextView = postView.findViewById(R.id.usernameDesc)
val description : TextView = postView.findViewById(R.id.description)
val nlikes : TextView = postView.findViewById(R.id.nlikes)
val nshares : TextView = postView.findViewById(R.id.nshares)
//Activate liker
post.activateLiker(holder, api, credential)
//Show comments
post.showComments(holder, api, credential)
//Activate Commenter
post.activateCommenter(holder, api, credential)
}
override fun getPreloadItems(position: Int): MutableList<Status> {
@ -132,3 +128,24 @@ class HomeFragment : FeedFragment<Status, HomeFragment.HomeRecyclerViewAdapter.V
}
}
}
/**
* Represents the posts that will be contained within the feed
*/
class ViewHolder(val postView: View, val context: android.content.Context) : RecyclerView.ViewHolder(postView) {
val profilePic : ImageView = postView.findViewById(R.id.profilePic)
val postPic : ImageView = postView.findViewById(R.id.postPicture)
val username : TextView = postView.findViewById(R.id.username)
val usernameDesc: TextView = postView.findViewById(R.id.usernameDesc)
val description : TextView = postView.findViewById(R.id.description)
val nlikes : TextView = postView.findViewById(R.id.nlikes)
val nshares : TextView = postView.findViewById(R.id.nshares)
val liker : ImageView = postView.findViewById(R.id.liker)
val submitCmnt : ImageButton = postView.findViewById(R.id.submitComment)
val commenter : ImageView = postView.findViewById(R.id.commenter)
val comment : EditText = postView.findViewById(R.id.editComment)
val commentCont : LinearLayout = postView.findViewById(R.id.commentContainer)
val commentIn : LinearLayout = postView.findViewById(R.id.commentIn)
val viewComment : TextView = postView.findViewById(R.id.ViewComments)
var isLiked : Boolean = false
}

View File

@ -46,8 +46,6 @@ class NotificationsFragment : FeedFragment<Notification, NotificationsFragment.N
val view = super.onCreateView(inflater, container, savedInstanceState)
content = makeContent()
//RequestBuilder that is re-used for every image
profilePicRequest = Glide.with(this)
.asDrawable().apply(RequestOptions().circleCrop())
@ -57,12 +55,6 @@ class NotificationsFragment : FeedFragment<Notification, NotificationsFragment.N
adapter = NotificationsRecyclerViewAdapter()
list.adapter = adapter
content.observe(viewLifecycleOwner,
Observer { c ->
adapter.submitList(c)
//after a refresh is done we need to stop the pull to refresh spinner
swipeRefreshLayout.isRefreshing = false
})
//Make Glide be aware of the recyclerview and pre-load images
val sizeProvider: ListPreloader.PreloadSizeProvider<Notification> = ViewPreloadSizeProvider()
@ -74,6 +66,18 @@ class NotificationsFragment : FeedFragment<Notification, NotificationsFragment.N
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
content = makeContent()
content.observe(viewLifecycleOwner,
Observer { c ->
adapter.submitList(c)
//after a refresh is done we need to stop the pull to refresh spinner
swipeRefreshLayout.isRefreshing = false
})
}
private fun makeContent(): LiveData<PagedList<Notification>> {
fun makeInitialCall(requestedLoadSize: Int): Call<List<Notification>> {
return pixelfedAPI

View File

@ -4,9 +4,19 @@ import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import com.bumptech.glide.RequestBuilder
import com.h.pixeldroid.R
import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.fragments.feeds.ViewHolder
import com.h.pixeldroid.utils.ImageConverter
import kotlinx.android.synthetic.main.post_fragment.view.*
import com.h.pixeldroid.utils.PostUtils.Companion.likePostCall
import com.h.pixeldroid.utils.PostUtils.Companion.postComment
import com.h.pixeldroid.utils.PostUtils.Companion.retrieveComments
import com.h.pixeldroid.utils.PostUtils.Companion.toggleCommentInput
import com.h.pixeldroid.utils.PostUtils.Companion.unLikePostCall
import java.io.Serializable
/*
@ -93,21 +103,26 @@ data class Status(
profilePic: ImageView
) {
//Setup username as a button that opens the profile
rootView.username.text = this.getUsername()
rootView.username.setTypeface(null, Typeface.BOLD)
rootView.username.setOnClickListener { account.openProfile(rootView.context) }
val username = rootView.findViewById<TextView>(R.id.username)
username.text = this.getUsername()
username.setTypeface(null, Typeface.BOLD)
username.setOnClickListener { account.openProfile(rootView.context) }
rootView.usernameDesc.text = this.getUsername()
rootView.usernameDesc.setTypeface(null, Typeface.BOLD)
val usernameDesc = rootView.findViewById<TextView>(R.id.usernameDesc)
usernameDesc.text = this.getUsername()
usernameDesc.setTypeface(null, Typeface.BOLD)
rootView.description.text = this.getDescription()
rootView.findViewById<TextView>(R.id.description).text = this.getDescription()
rootView.nlikes.text = this.getNLikes()
rootView.nlikes.setTypeface(null, Typeface.BOLD)
val nlikes = rootView.findViewById<TextView>(R.id.nlikes)
nlikes.text = this.getNLikes()
nlikes.setTypeface(null, Typeface.BOLD)
rootView.nshares.text = this.getNShares()
rootView.nshares.setTypeface(null, Typeface.BOLD)
val nshares = rootView.findViewById<TextView>(R.id.nshares)
nshares.text = this.getNShares()
nshares.setTypeface(null, Typeface.BOLD)
//Setup images
request.load(this.getPostUrl()).into(postPic)
ImageConverter.setRoundImageFromURL(
rootView,
@ -115,6 +130,67 @@ data class Status(
profilePic
)
profilePic.setOnClickListener { account.openProfile(rootView.context) }
//Set comment initial visibility
rootView.findViewById<LinearLayout>(R.id.commentIn).visibility = View.GONE
}
fun activateLiker(
holder : ViewHolder,
api: PixelfedAPI,
credential: String
) {
//Activate the liker
holder.liker.setOnClickListener {
if (holder.isLiked) {
//Unlike the post
unLikePostCall(holder, api, credential, this)
} else {
//like the post
likePostCall(holder, api, credential, this)
}
}
}
fun showComments(
holder : ViewHolder,
api: PixelfedAPI,
credential: String
) {
//Show all comments of a post
if (replies_count == 0) {
holder.viewComment.text = "No comments on this post..."
} else {
holder.viewComment.text = "View all ${replies_count} comments..."
holder.viewComment.setOnClickListener {
holder.viewComment.visibility = View.GONE
//Retrieve the comments
retrieveComments(holder, api, credential, this)
}
}
}
fun activateCommenter(
holder : ViewHolder,
api: PixelfedAPI,
credential: String
) {
//Toggle comment button
toggleCommentInput(holder)
//Activate commenter
holder.submitCmnt.setOnClickListener {
val textIn = holder.comment.text
//Open text input
if(textIn.isNullOrEmpty()) {
Toast.makeText(holder.context,"Comment must not be empty!", Toast.LENGTH_SHORT).show()
} else {
//Post the comment
postComment(holder, api, credential, this)
}
}
}
enum class Visibility : Serializable {

View File

@ -77,8 +77,8 @@ class ImageConverter {
* @param view, the view in which this is happening
* @param image, the imageView into which we will load the image
*/
fun setDefaultImage(view : View, image : ImageView) {
Glide.with(view).load(R.drawable.ic_default_user).into(image)
fun setImageFromDrawable(view : View, image : ImageView, drawable : Int) {
Glide.with(view).load(drawable).into(image)
}
}
}

View File

@ -0,0 +1,163 @@
package com.h.pixeldroid.utils
import android.graphics.Typeface
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.cardview.widget.CardView
import com.h.pixeldroid.R
import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.fragments.feeds.ViewHolder
import com.h.pixeldroid.objects.Account
import com.h.pixeldroid.objects.Context
import com.h.pixeldroid.objects.Status
import kotlinx.android.synthetic.main.comment.view.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class PostUtils {
companion object {
fun toggleCommentInput(
holder : ViewHolder
) {
//Toggle comment button
holder.commenter.setOnClickListener {
when(holder.commentIn.visibility) {
View.VISIBLE -> holder.commentIn.visibility = View.GONE
View.INVISIBLE -> holder.commentIn.visibility = View.VISIBLE
View.GONE -> holder.commentIn.visibility = View.VISIBLE
}
}
}
fun likePostCall(
holder : ViewHolder,
api: PixelfedAPI,
credential: String,
post : Status
) {
api.likePost(credential, post.id).enqueue(object : Callback<Status> {
override fun onFailure(call: Call<Status>, t: Throwable) {
Log.e("LIKE ERROR", t.toString())
}
override fun onResponse(call: Call<Status>, response: Response<Status>) {
if(response.code() == 200) {
val resp = response.body()!!
//Update shown like count and internal like toggle
holder.nlikes.text = resp.getNLikes()
holder.isLiked = resp.favourited
} else {
Log.e("RESPOSE_CODE", response.code().toString())
}
}
})
}
fun unLikePostCall(
holder : ViewHolder,
api: PixelfedAPI,
credential: String,
post : Status
) {
api.unlikePost(credential, post.id).enqueue(object : Callback<Status> {
override fun onFailure(call: Call<Status>, t: Throwable) {
Log.e("UNLIKE ERROR", t.toString())
}
override fun onResponse(call: Call<Status>, response: Response<Status>) {
if(response.code() == 200) {
val resp = response.body()!!
//Update shown like count and internal like toggle
holder.nlikes.text = resp.getNLikes()
holder.isLiked = resp.favourited
} else {
Log.e("RESPOSE_CODE", response.code().toString())
}
}
})
}
fun postComment(
holder : ViewHolder,
api: PixelfedAPI,
credential: String,
post : Status
) {
val textIn = holder.comment.text
val nonNullText = textIn.toString()
api.postStatus(credential, nonNullText, post.id).enqueue(object :
Callback<Status> {
override fun onFailure(call: Call<Status>, t: Throwable) {
Log.e("COMMENT ERROR", t.toString())
Toast.makeText(holder.context,"Comment error!", Toast.LENGTH_SHORT).show()
}
override fun onResponse(call: Call<Status>, response: Response<Status>) {
//Check that the received response code is valid
if(response.code() == 200) {
val resp = response.body()!!
holder.commentIn.visibility = View.GONE
//Add the comment to the comment section
addComment(holder.context, holder.commentCont, resp.account.username, resp.content)
Toast.makeText(holder.context,"Comment: \"$textIn\" posted!", Toast.LENGTH_SHORT).show()
Log.e("COMMENT SUCCESS", "posted: $textIn")
} else {
Log.e("ERROR_CODE", response.code().toString())
}
}
})
}
fun addComment(context: android.content.Context, commentContainer: LinearLayout, commentUsername: String, commentContent: String) {
val view = LayoutInflater.from(context)
.inflate(R.layout.comment, commentContainer, true)
view.user.text = commentUsername
view.commentText.text = commentContent
}
fun retrieveComments(
holder : ViewHolder,
api: PixelfedAPI,
credential: String,
post : Status
) {
api.statusComments(post.id, credential).enqueue(object :
Callback<Context> {
override fun onFailure(call: Call<Context>, t: Throwable) {
Log.e("COMMENT FETCH ERROR", t.toString())
}
override fun onResponse(
call: Call<Context>,
response: Response<Context>
) {
if(response.code() == 200) {
val statuses = response.body()!!.descendants
//Create the new views for each comment
for (status in statuses) {
addComment(holder.context, holder.commentCont, status.account.username, status.content)
}
} else {
Log.e("COMMENT ERROR", "${response.code()} with body ${response.errorBody()}")
}
}
})
}
}
}

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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_margin="20dp">
<androidx.cardview.widget.CardView
android:id="@+id/comment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/user"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="8"
android:textStyle="bold"
tools:text="username" />
<TextView
android:id="@+id/commentText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
tools:text="This is a comment on this awesome post" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,66 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.cardview.widget.CardView 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/notification"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_margin="5dp">
<TextView
android:id="@+id/notification_type"
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/notification"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:drawableStart="@drawable/ic_heart"
android:drawablePadding="6dp"
android:gravity="center_vertical"
android:paddingStart="38dp"
android:textColor="?android:textColorTertiary"
android:textStyle="bold"
app:layout_constraintLeft_toRightOf="parent"
app:layout_constraintRight_toLeftOf="parent"
app:layout_constraintStart_toEndOf="@+id/notification_avatar"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="RtlSymmetry"
tools:text="User liked your post"
tools:visibility="visible" />
android:layout_margin="8dp">
<ImageView
android:id="@+id/notification_avatar"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="14dp"
android:layout_marginTop="14dp"
android:scaleType="centerCrop"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/notification_type"
tools:src="@drawable/ic_default_user" />
<TextView
android:id="@+id/notification_type"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableStart="@drawable/ic_heart"
android:drawablePadding="6dp"
android:gravity="center_vertical"
android:paddingStart="38dp"
android:textColor="?android:textColorTertiary"
android:textStyle="bold"
app:layout_constraintLeft_toRightOf="parent"
app:layout_constraintRight_toLeftOf="parent"
app:layout_constraintStart_toEndOf="@+id/notification_avatar"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="RtlSymmetry"
tools:text="User liked your post"
tools:visibility="visible" />
<ImageView
android:id="@+id/notification_photo_thumbnail"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="14dp"
android:scaleType="centerCrop"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/notification_type"
tools:src="@drawable/ic_default_user"
tools:srcCompat="@tools:sample/backgrounds/scenic" />
<ImageView
android:id="@+id/notification_avatar"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="14dp"
android:layout_marginTop="14dp"
android:scaleType="centerCrop"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/notification_type"
tools:src="@drawable/ic_default_user" />
<TextView
android:id="@+id/notification_post_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/notification_photo_thumbnail"
app:layout_constraintHorizontal_bias="0.164"
app:layout_constraintStart_toEndOf="@+id/notification_avatar"
app:layout_constraintTop_toBottomOf="@+id/notification_type"
app:layout_constraintVertical_bias="0.408"
tools:text="Post description" />
<ImageView
android:id="@+id/notification_photo_thumbnail"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="14dp"
android:scaleType="centerCrop"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/notification_type"
tools:src="@drawable/ic_default_user"
tools:srcCompat="@tools:sample/backgrounds/scenic" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/notification_post_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/notification_photo_thumbnail"
app:layout_constraintHorizontal_bias="0.164"
app:layout_constraintStart_toEndOf="@+id/notification_avatar"
app:layout_constraintTop_toBottomOf="@+id/notification_type"
app:layout_constraintVertical_bias="0.408"
tools:text="Post description" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

View File

@ -1,127 +1,200 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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="wrap_content"
tools:context=".fragments.PostFragment">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context=".fragments.PostFragment">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/linearLayout3"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.cardview.widget.CardView
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp">
android:orientation="vertical">
<ImageView
android:id="@+id/profilePic"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/ic_default_user"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/profilePic"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.516"
tools:text="TextView" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraintLayout2"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/postPicture"
<LinearLayout
android:id="@+id/linearLayout3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
app:layout_constraintTop_toTopOf="parent" />
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp">
<ImageView
android:id="@+id/profilePic"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/ic_default_user"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/profilePic"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.516"
tools:text="TextView" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraintLayout2"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/postPicture"
android:adjustViewBounds="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/LikeShareConstraint"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_marginTop="10dp"
>
<LinearLayout
android:layout_width="409dp"
android:layout_height="28dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/liker"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:src="@drawable/ic_heart" />
<ImageView
android:id="@+id/commenter"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:src="@drawable/ic_add_black_24dp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/nlikes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="30dp"
android:layout_marginTop="0dp"
android:layout_marginBottom="10dp"
android:layout_weight="50"
tools:text="TextView" />
<TextView
android:id="@+id/nshares"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:layout_marginRight="30dp"
android:layout_marginBottom="10dp"
android:layout_weight="50"
android:gravity="right"
tools:text="TextView" />
</LinearLayout>
<TextView
android:id="@+id/usernameDesc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
tools:text="TextView" />
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginBottom="10dp"
tools:text="TextView" />
<LinearLayout
android:id="@+id/commentIn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:layout_marginStart="10dp"
android:orientation="horizontal">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="3">
<EditText
android:id="@+id/editComment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="Comment" />
</com.google.android.material.textfield.TextInputLayout>
<ImageButton
android:id="@+id/submitComment"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
android:src="@drawable/ic_add_black_24dp"
android:layout_weight="1"
android:contentDescription="Submit button" />
</LinearLayout>
<LinearLayout
android:id="@+id/commentContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/ViewComments"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginBottom="10dp"
tools:text="TextView">
</TextView>
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="1dp">
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/nlikes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="10dp"
android:layout_weight="50"
tools:text="TextView" />
<TextView
android:id="@+id/nshares"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginEnd="30dp"
android:layout_marginBottom="10dp"
android:layout_weight="50"
android:gravity="end"
tools:text="TextView" />
</LinearLayout>
<TextView
android:id="@+id/usernameDesc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
tools:text="TextView" />
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
tools:text="TextView" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="30dp">
<ImageView
android:id="@+id/imageView2"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:src="@android:drawable/screen_background_dark"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</ScrollView>
</FrameLayout>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item type="id" name="comment" />
</resources>

View File

@ -8,7 +8,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.1'
classpath 'com.android.tools.build:gradle:3.6.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong