Reblogging and HTML text (#107)
* Changed Post interaction icons and added click feedback * added reblog and unreblog api implementations * Use fancy animated buttons * WIP reposter * WIP reblog button * renamed ViewHolder => PostViewHolder * activated reblogger in feed * added custom html parser, still need to fix clickable links * added parsed HTML in notifications * fixed mention click * added tests for reblog and clickable mentions * adapted unit tests to work with new html parser * changed incoherent comment * made hashtags slightly less useless * removed unit test that were no longer valid * removed useless test * trying to fix tests * fixing tests * trying to improve coverage a little * removed unused code to improve coverage * changed cast to type converter * added failure responses to help coverage * added mock server response for reblogging * fixed broken json * trying to fix a broken test * Tweak tests * Typo in test * Add scrolls to make tests pass on small screens * fixed old JSON in mockserver * fixed linter issue Co-authored-by: Matthieu <61561059+Wv5twkFEKh54vo4tta9yu7dHa3@users.noreply.github.com>
This commit is contained in:
parent
8950d4c162
commit
8265ac2d62
|
@ -64,6 +64,7 @@ dependencies {
|
||||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||||
implementation "androidx.browser:browser:1.2.0"
|
implementation "androidx.browser:browser:1.2.0"
|
||||||
implementation 'com.google.android.material:material:1.1.0'
|
implementation 'com.google.android.material:material:1.1.0'
|
||||||
|
implementation 'com.github.connyduck:sparkbutton:3.0.0'
|
||||||
|
|
||||||
def room_version = "2.2.5"
|
def room_version = "2.2.5"
|
||||||
implementation "androidx.room:room-runtime:$room_version"
|
implementation "androidx.room:room-runtime:$room_version"
|
||||||
|
|
|
@ -2,19 +2,39 @@ package com.h.pixeldroid
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.text.SpannableString
|
||||||
|
import android.text.style.ClickableSpan
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.test.core.app.ActivityScenario
|
import androidx.test.core.app.ActivityScenario
|
||||||
import androidx.test.espresso.Espresso
|
import androidx.test.espresso.Espresso
|
||||||
|
import androidx.test.espresso.Espresso.onView
|
||||||
|
import androidx.test.espresso.NoMatchingViewException
|
||||||
|
import androidx.test.espresso.UiController
|
||||||
|
import androidx.test.espresso.ViewAction
|
||||||
import androidx.test.espresso.action.ViewActions
|
import androidx.test.espresso.action.ViewActions
|
||||||
|
import androidx.test.espresso.assertion.ViewAssertions
|
||||||
|
import androidx.test.espresso.contrib.RecyclerViewActions
|
||||||
|
import androidx.test.espresso.intent.Intents
|
||||||
import androidx.test.espresso.intent.Intents.intended
|
import androidx.test.espresso.intent.Intents.intended
|
||||||
|
import androidx.test.espresso.intent.matcher.BundleMatchers.hasValue
|
||||||
import androidx.test.espresso.intent.matcher.IntentMatchers
|
import androidx.test.espresso.intent.matcher.IntentMatchers
|
||||||
import androidx.test.espresso.intent.rule.IntentsTestRule
|
import androidx.test.espresso.intent.rule.IntentsTestRule
|
||||||
import androidx.test.espresso.matcher.ViewMatchers
|
import androidx.test.espresso.matcher.ViewMatchers
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import androidx.test.rule.ActivityTestRule
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
|
import com.h.pixeldroid.fragments.feeds.PostViewHolder
|
||||||
|
import com.h.pixeldroid.objects.Account
|
||||||
|
import com.h.pixeldroid.objects.Account.Companion.ACCOUNT_TAG
|
||||||
|
import com.h.pixeldroid.testUtility.CustomMatchers
|
||||||
|
import com.h.pixeldroid.testUtility.CustomMatchers.Companion.first
|
||||||
import com.h.pixeldroid.testUtility.MockServer
|
import com.h.pixeldroid.testUtility.MockServer
|
||||||
import org.hamcrest.CoreMatchers
|
import org.hamcrest.CoreMatchers
|
||||||
import org.hamcrest.Matcher
|
import org.hamcrest.Matcher
|
||||||
|
import org.hamcrest.Matchers
|
||||||
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -32,7 +52,7 @@ class IntentTest {
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
var mLoginActivityActivityTestRule =
|
var mLoginActivityActivityTestRule =
|
||||||
IntentsTestRule(
|
ActivityTestRule(
|
||||||
LoginActivity::class.java
|
LoginActivity::class.java
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -44,8 +64,87 @@ class IntentTest {
|
||||||
.targetContext.getSharedPreferences("com.h.pixeldroid.pref", Context.MODE_PRIVATE)
|
.targetContext.getSharedPreferences("com.h.pixeldroid.pref", Context.MODE_PRIVATE)
|
||||||
preferences.edit().putString("accessToken", "azerty").apply()
|
preferences.edit().putString("accessToken", "azerty").apply()
|
||||||
preferences.edit().putString("domain", baseUrl.toString()).apply()
|
preferences.edit().putString("domain", baseUrl.toString()).apply()
|
||||||
|
Intents.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun clickingMentionOpensProfile() {
|
||||||
|
ActivityScenario.launch(MainActivity::class.java)
|
||||||
|
|
||||||
|
val account = Account("1450", "deerbard_photo", "deerbard_photo",
|
||||||
|
"https://pixelfed.social/deerbard_photo", "deerbard photography",
|
||||||
|
"",
|
||||||
|
"https://pixelfed.social/storage/avatars/000/000/001/450/SMSep5NoabDam1W8UDMh_avatar.png?v=4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a",
|
||||||
|
"https://pixelfed.social/storage/avatars/000/000/001/450/SMSep5NoabDam1W8UDMh_avatar.png?v=4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a",
|
||||||
|
"", "", false, emptyList(), false,
|
||||||
|
"2018-08-01T12:58:21.000000Z", 72, 68, 27,
|
||||||
|
null, null, false, null)
|
||||||
|
val expectedIntent: Matcher<Intent> = CoreMatchers.allOf(
|
||||||
|
IntentMatchers.hasExtra(ACCOUNT_TAG, account)
|
||||||
|
)
|
||||||
|
|
||||||
|
Thread.sleep(1000)
|
||||||
|
|
||||||
|
//Click the mention
|
||||||
|
Espresso.onView(ViewMatchers.withId(R.id.list))
|
||||||
|
.perform(RecyclerViewActions.actionOnItemAtPosition<PostViewHolder>
|
||||||
|
(0, clickClickableSpanInDescription("@Dobios")))
|
||||||
|
|
||||||
|
//Wait a bit
|
||||||
|
Thread.sleep(1000)
|
||||||
|
|
||||||
|
//Check that the Profile is shown
|
||||||
|
intended(expectedIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clickClickableSpanInDescription(textToClick: CharSequence): ViewAction {
|
||||||
|
return object : ViewAction {
|
||||||
|
|
||||||
|
override fun getConstraints(): Matcher<View> {
|
||||||
|
return Matchers.instanceOf(TextView::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDescription(): String {
|
||||||
|
return "clicking on a ClickableSpan";
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun perform(uiController: UiController, view: View) {
|
||||||
|
val textView = view.findViewById<View>(R.id.description) as TextView
|
||||||
|
val spannableString = textView.text as SpannableString
|
||||||
|
|
||||||
|
if (spannableString.isEmpty()) {
|
||||||
|
// TextView is empty, nothing to do
|
||||||
|
throw NoMatchingViewException.Builder()
|
||||||
|
.includeViewHierarchy(true)
|
||||||
|
.withRootView(textView)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the links inside the TextView and check if we find textToClick
|
||||||
|
val spans = spannableString.getSpans(0, spannableString.length, ClickableSpan::class.java)
|
||||||
|
if (spans.isNotEmpty()) {
|
||||||
|
var spanCandidate: ClickableSpan
|
||||||
|
for (span: ClickableSpan in spans) {
|
||||||
|
spanCandidate = span
|
||||||
|
val start = spannableString.getSpanStart(spanCandidate)
|
||||||
|
val end = spannableString.getSpanEnd(spanCandidate)
|
||||||
|
val sequence = spannableString.subSequence(start, end)
|
||||||
|
if (textToClick.toString().equals(sequence.toString())) {
|
||||||
|
span.onClick(textView)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// textToClick not found in TextView
|
||||||
|
throw NoMatchingViewException.Builder()
|
||||||
|
.includeViewHierarchy(true)
|
||||||
|
.withRootView(textView)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun launchesIntent() {
|
fun launchesIntent() {
|
||||||
|
@ -63,7 +162,10 @@ class IntentTest {
|
||||||
Thread.sleep(1000)
|
Thread.sleep(1000)
|
||||||
|
|
||||||
intended(expectedIntent)
|
intended(expectedIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun after() {
|
||||||
|
Intents.release()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -5,10 +5,13 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.Intent.ACTION_VIEW
|
import android.content.Intent.ACTION_VIEW
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import androidx.test.core.app.ActivityScenario
|
||||||
import androidx.test.espresso.Espresso.onView
|
import androidx.test.espresso.Espresso.onView
|
||||||
import androidx.test.espresso.action.ViewActions
|
import androidx.test.espresso.action.ViewActions
|
||||||
import androidx.test.espresso.action.ViewActions.click
|
import androidx.test.espresso.action.ViewActions.click
|
||||||
|
import androidx.test.espresso.action.ViewActions.scrollTo
|
||||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
|
import androidx.test.espresso.intent.Intents
|
||||||
import androidx.test.espresso.intent.Intents.intended
|
import androidx.test.espresso.intent.Intents.intended
|
||||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
|
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
|
||||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasDataString
|
import androidx.test.espresso.intent.matcher.IntentMatchers.hasDataString
|
||||||
|
@ -24,6 +27,7 @@ import org.hamcrest.CoreMatchers.allOf
|
||||||
import org.hamcrest.CoreMatchers.anyOf
|
import org.hamcrest.CoreMatchers.anyOf
|
||||||
import org.hamcrest.CoreMatchers.containsString
|
import org.hamcrest.CoreMatchers.containsString
|
||||||
import org.hamcrest.Matcher
|
import org.hamcrest.Matcher
|
||||||
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -52,14 +56,14 @@ class LoginInstrumentedTest {
|
||||||
@Test
|
@Test
|
||||||
fun invalidURL() {
|
fun invalidURL() {
|
||||||
onView(withId(R.id.editText)).perform(ViewActions.replaceText("/jdi"), ViewActions.closeSoftKeyboard())
|
onView(withId(R.id.editText)).perform(ViewActions.replaceText("/jdi"), ViewActions.closeSoftKeyboard())
|
||||||
onView(withId(R.id.connect_instance_button)).perform(click())
|
onView(withId(R.id.connect_instance_button)).perform(scrollTo()).perform(click())
|
||||||
onView(withId(R.id.editText)).check(matches(hasErrorText("Invalid domain")))
|
onView(withId(R.id.editText)).check(matches(hasErrorText("Invalid domain")))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun notPixelfedInstance() {
|
fun notPixelfedInstance() {
|
||||||
onView(withId(R.id.editText)).perform(ViewActions.replaceText("localhost"), ViewActions.closeSoftKeyboard())
|
onView(withId(R.id.editText)).perform(ViewActions.replaceText("localhost"), ViewActions.closeSoftKeyboard())
|
||||||
onView(withId(R.id.connect_instance_button)).perform(click())
|
onView(withId(R.id.connect_instance_button)).perform(scrollTo()).perform(click())
|
||||||
onView(withId(R.id.editText)).check(matches(hasErrorText("Could not register the application with this server")))
|
onView(withId(R.id.editText)).check(matches(hasErrorText("Could not register the application with this server")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,37 +73,50 @@ class LoginCheckIntent {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
var globalTimeout: Timeout = Timeout.seconds(100)
|
var globalTimeout: Timeout = Timeout.seconds(100)
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val intentsTestRule = IntentsTestRule(LoginActivity::class.java)
|
val intentsTestRule = ActivityTestRule(LoginActivity::class.java)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun before() {
|
||||||
|
Intents.init()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun launchesOAuthIntent() {
|
fun launchesOAuthIntent() {
|
||||||
|
ActivityScenario.launch(LoginActivity::class.java)
|
||||||
val expectedIntent: Matcher<Intent> = allOf(
|
val expectedIntent: Matcher<Intent> = allOf(
|
||||||
hasAction(ACTION_VIEW),
|
hasAction(ACTION_VIEW),
|
||||||
hasDataString(containsString("pixelfed.social"))
|
hasDataString(containsString("pixelfed.social"))
|
||||||
)
|
)
|
||||||
|
Thread.sleep(1000)
|
||||||
|
|
||||||
onView(withId(R.id.editText)).perform(ViewActions.replaceText("pixelfed.social"), ViewActions.closeSoftKeyboard())
|
onView(withId(R.id.editText)).perform(scrollTo()).perform(ViewActions.replaceText("pixelfed.social"), ViewActions.closeSoftKeyboard())
|
||||||
onView(withId(R.id.connect_instance_button)).perform(click())
|
onView(withId(R.id.connect_instance_button)).perform(scrollTo()).perform(click())
|
||||||
|
|
||||||
Thread.sleep(5000)
|
Thread.sleep(3000)
|
||||||
|
|
||||||
intended(expectedIntent)
|
intended(expectedIntent)
|
||||||
|
|
||||||
}
|
}
|
||||||
@Test
|
@Test
|
||||||
fun launchesInstanceInfo() {
|
fun launchesInstanceInfo() {
|
||||||
|
ActivityScenario.launch(LoginActivity::class.java)
|
||||||
val expectedIntent: Matcher<Intent> = allOf(
|
val expectedIntent: Matcher<Intent> = allOf(
|
||||||
hasAction(ACTION_VIEW),
|
hasAction(ACTION_VIEW),
|
||||||
hasDataString(containsString("pixelfed.org/join"))
|
hasDataString(containsString("pixelfed.org/join"))
|
||||||
)
|
)
|
||||||
|
|
||||||
onView(withId(R.id.whatsAnInstanceTextView)).perform(click())
|
onView(withId(R.id.whatsAnInstanceTextView)).perform(scrollTo()).perform(click())
|
||||||
|
|
||||||
Thread.sleep(1000)
|
Thread.sleep(10000)
|
||||||
|
|
||||||
intended(expectedIntent)
|
intended(expectedIntent)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun after() {
|
||||||
|
Intents.release()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
|
|
@ -1,29 +1,23 @@
|
||||||
package com.h.pixeldroid
|
package com.h.pixeldroid
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.Gravity
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.EditText
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.test.core.app.ActivityScenario
|
import androidx.test.core.app.ActivityScenario
|
||||||
import androidx.test.espresso.Espresso.onView
|
import androidx.test.espresso.Espresso.onView
|
||||||
import androidx.test.espresso.UiController
|
import androidx.test.espresso.action.ViewActions
|
||||||
import androidx.test.espresso.ViewAction
|
|
||||||
import androidx.test.espresso.action.*
|
|
||||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
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.contrib.RecyclerViewActions.actionOnItemAtPosition
|
import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition
|
||||||
import androidx.test.espresso.matcher.ViewMatchers.*
|
import androidx.test.espresso.matcher.ViewMatchers.*
|
||||||
import androidx.test.ext.junit.rules.ActivityScenarioRule
|
import androidx.test.ext.junit.rules.ActivityScenarioRule
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
import com.h.pixeldroid.fragments.feeds.ViewHolder
|
import com.h.pixeldroid.fragments.feeds.PostViewHolder
|
||||||
|
import com.h.pixeldroid.testUtility.CustomMatchers.Companion.clickChildViewWithId
|
||||||
|
import com.h.pixeldroid.testUtility.CustomMatchers.Companion.first
|
||||||
|
import com.h.pixeldroid.testUtility.CustomMatchers.Companion.getText
|
||||||
|
import com.h.pixeldroid.testUtility.CustomMatchers.Companion.slowSwipeUp
|
||||||
|
import com.h.pixeldroid.testUtility.CustomMatchers.Companion.typeTextInViewWithId
|
||||||
import com.h.pixeldroid.testUtility.MockServer
|
import com.h.pixeldroid.testUtility.MockServer
|
||||||
import org.hamcrest.BaseMatcher
|
|
||||||
import org.hamcrest.Matcher
|
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -33,84 +27,6 @@ import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class MockedServerTest {
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val mockServer = MockServer()
|
private val mockServer = MockServer()
|
||||||
|
|
||||||
|
@ -234,22 +150,43 @@ class MockedServerTest {
|
||||||
Thread.sleep(1000)
|
Thread.sleep(1000)
|
||||||
|
|
||||||
//Get initial like count
|
//Get initial like count
|
||||||
val likes = getText(withId(R.id.nlikes))
|
val likes = getText(first(withId(R.id.nlikes)))
|
||||||
|
|
||||||
//Like the post
|
//Like the post
|
||||||
onView(withId(R.id.list))
|
onView(withId(R.id.list))
|
||||||
.perform(actionOnItemAtPosition<ViewHolder>
|
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||||
(0, clickChildViewWithId(R.id.liker)))
|
(0, clickChildViewWithId(R.id.liker)))
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
//Unlike the post
|
//Unlike the post
|
||||||
onView(withId(R.id.list))
|
onView(withId(R.id.list))
|
||||||
.perform(actionOnItemAtPosition<ViewHolder>
|
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||||
(0, clickChildViewWithId(R.id.liker)))
|
(0, clickChildViewWithId(R.id.liker)))
|
||||||
//...
|
//...
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
|
|
||||||
//Profit
|
//Profit
|
||||||
onView(withId(R.id.nlikes)).check(matches((withText(likes))))
|
onView(first(withId(R.id.nlikes))).check(matches((withText(likes))))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun clickingLikeButtonFails() {
|
||||||
|
ActivityScenario.launch(MainActivity::class.java)
|
||||||
|
Thread.sleep(1000)
|
||||||
|
|
||||||
|
//Get initial like count
|
||||||
|
val likes = getText(first(withId(R.id.nlikes)))
|
||||||
|
|
||||||
|
//Like the post
|
||||||
|
onView(withId(R.id.list))
|
||||||
|
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||||
|
(2, clickChildViewWithId(R.id.liker)))
|
||||||
|
Thread.sleep(100)
|
||||||
|
|
||||||
|
//...
|
||||||
|
Thread.sleep(100)
|
||||||
|
|
||||||
|
//Profit
|
||||||
|
onView((withId(R.id.list))).check(matches(isDisplayed()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -259,7 +196,7 @@ class MockedServerTest {
|
||||||
|
|
||||||
//Get initial like count
|
//Get initial like count
|
||||||
onView(withId(R.id.list))
|
onView(withId(R.id.list))
|
||||||
.perform(actionOnItemAtPosition<ViewHolder>
|
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||||
(0, clickChildViewWithId(R.id.username)))
|
(0, clickChildViewWithId(R.id.username)))
|
||||||
|
|
||||||
Thread.sleep(1000)
|
Thread.sleep(1000)
|
||||||
|
@ -275,7 +212,7 @@ class MockedServerTest {
|
||||||
|
|
||||||
//Get initial like count
|
//Get initial like count
|
||||||
onView(withId(R.id.list))
|
onView(withId(R.id.list))
|
||||||
.perform(actionOnItemAtPosition<ViewHolder>
|
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||||
(0, clickChildViewWithId(R.id.profilePic)))
|
(0, clickChildViewWithId(R.id.profilePic)))
|
||||||
|
|
||||||
Thread.sleep(1000)
|
Thread.sleep(1000)
|
||||||
|
@ -284,16 +221,86 @@ class MockedServerTest {
|
||||||
onView(withId(R.id.accountNameTextView)).check(matches(isDisplayed()))
|
onView(withId(R.id.accountNameTextView)).check(matches(isDisplayed()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun clickingReblogButtonWorks() {
|
||||||
|
ActivityScenario.launch(MainActivity::class.java)
|
||||||
|
Thread.sleep(1000)
|
||||||
|
|
||||||
|
//Get initial like count
|
||||||
|
val shares = getText(first(withId(R.id.nshares)))
|
||||||
|
|
||||||
|
//Reblog the post
|
||||||
|
onView(withId(R.id.list))
|
||||||
|
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||||
|
(0, clickChildViewWithId(R.id.reblogger)))
|
||||||
|
Thread.sleep(100)
|
||||||
|
|
||||||
|
//UnReblog the post
|
||||||
|
onView(withId(R.id.list))
|
||||||
|
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||||
|
(0, clickChildViewWithId(R.id.reblogger)))
|
||||||
|
//...
|
||||||
|
Thread.sleep(100)
|
||||||
|
|
||||||
|
//Profit
|
||||||
|
onView(first(withId(R.id.nshares))).check(matches((withText(shares))))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun clickingMentionOpensProfile() {
|
||||||
|
ActivityScenario.launch(MainActivity::class.java)
|
||||||
|
Thread.sleep(1000)
|
||||||
|
|
||||||
|
//Click the mention
|
||||||
|
onView(withId(R.id.list))
|
||||||
|
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||||
|
(0, clickChildViewWithId(R.id.description)))
|
||||||
|
|
||||||
|
//Wait a bit
|
||||||
|
Thread.sleep(1000)
|
||||||
|
|
||||||
|
//Check that the Profile is shown
|
||||||
|
onView(first(withId(R.id.username))).check(matches(isDisplayed()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun clickingHashTagsWorks() {
|
||||||
|
ActivityScenario.launch(MainActivity::class.java)
|
||||||
|
Thread.sleep(1000)
|
||||||
|
|
||||||
|
//Click the hashtag
|
||||||
|
onView(withId(R.id.list))
|
||||||
|
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||||
|
(1, clickChildViewWithId(R.id.description)))
|
||||||
|
|
||||||
|
//Wait a bit
|
||||||
|
Thread.sleep(1000)
|
||||||
|
|
||||||
|
//Check that the HashTag was indeed clicked
|
||||||
|
//Doesn't do anything for now
|
||||||
|
onView(withId(R.id.list)).check(matches(isDisplayed()))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun clickingCommentButtonOpensCommentSection() {
|
fun clickingCommentButtonOpensCommentSection() {
|
||||||
ActivityScenario.launch(MainActivity::class.java)
|
ActivityScenario.launch(MainActivity::class.java)
|
||||||
Thread.sleep(1000)
|
Thread.sleep(1000)
|
||||||
//Click comment button and then try to see if the commenter exists
|
|
||||||
|
//Click comment button 3 times and then try to see if the commenter exists
|
||||||
onView(withId(R.id.list))
|
onView(withId(R.id.list))
|
||||||
.perform(actionOnItemAtPosition<ViewHolder>
|
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||||
(0, clickChildViewWithId(R.id.commenter)))
|
(0, clickChildViewWithId(R.id.commenter)))
|
||||||
Thread.sleep(1000)
|
Thread.sleep(100)
|
||||||
onView(withId(R.id.commentIn))
|
onView(withId(R.id.list))
|
||||||
|
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||||
|
(0, clickChildViewWithId(R.id.commenter)))
|
||||||
|
Thread.sleep(100)
|
||||||
|
onView(withId(R.id.list))
|
||||||
|
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||||
|
(0, clickChildViewWithId(R.id.commenter)))
|
||||||
|
|
||||||
|
onView(first(withId(R.id.commentIn)))
|
||||||
.check(matches(hasDescendant(withId(R.id.editComment))))
|
.check(matches(hasDescendant(withId(R.id.editComment))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,13 +310,25 @@ class MockedServerTest {
|
||||||
Thread.sleep(1000)
|
Thread.sleep(1000)
|
||||||
//Open the comment section
|
//Open the comment section
|
||||||
onView(withId(R.id.list))
|
onView(withId(R.id.list))
|
||||||
.perform(actionOnItemAtPosition<ViewHolder>
|
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||||
(0, clickChildViewWithId(R.id.ViewComments)))
|
(0, clickChildViewWithId(R.id.ViewComments)))
|
||||||
Thread.sleep(1000)
|
Thread.sleep(1000)
|
||||||
onView(withId(R.id.commentContainer))
|
onView(first(withId(R.id.commentContainer)))
|
||||||
.check(matches(hasDescendant(withId(R.id.comment))))
|
.check(matches(hasDescendant(withId(R.id.comment))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun clickingViewCommentFails() {
|
||||||
|
ActivityScenario.launch(MainActivity::class.java)
|
||||||
|
Thread.sleep(1000)
|
||||||
|
//Open the comment section
|
||||||
|
onView(withId(R.id.list))
|
||||||
|
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||||
|
(2, clickChildViewWithId(R.id.ViewComments)))
|
||||||
|
Thread.sleep(1000)
|
||||||
|
onView(withId(R.id.list)).check(matches(isDisplayed()))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun postingACommentWorks() {
|
fun postingACommentWorks() {
|
||||||
ActivityScenario.launch(MainActivity::class.java)
|
ActivityScenario.launch(MainActivity::class.java)
|
||||||
|
@ -317,23 +336,21 @@ class MockedServerTest {
|
||||||
|
|
||||||
//Open the comment section
|
//Open the comment section
|
||||||
onView(withId(R.id.list))
|
onView(withId(R.id.list))
|
||||||
.perform(actionOnItemAtPosition<ViewHolder>
|
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||||
(0, clickChildViewWithId(R.id.commenter)))
|
(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))
|
onView(withId(R.id.list)).perform(slowSwipeUp(false))
|
||||||
Thread.sleep(1000)
|
Thread.sleep(1000)
|
||||||
|
|
||||||
onView(withId(R.id.list))
|
onView(withId(R.id.list))
|
||||||
.perform(actionOnItemAtPosition<ViewHolder>
|
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||||
(0, typeTextInViewWithId(R.id.editComment, "test")))
|
(0, typeTextInViewWithId(R.id.editComment, "test")))
|
||||||
onView(withId(R.id.list))
|
onView(withId(R.id.list))
|
||||||
.perform(actionOnItemAtPosition<ViewHolder>
|
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||||
(0, clickChildViewWithId(R.id.submitComment)))
|
(0, clickChildViewWithId(R.id.submitComment)))
|
||||||
|
|
||||||
Thread.sleep(1000)
|
Thread.sleep(1000)
|
||||||
onView(withId(R.id.commentContainer))
|
onView(first(withId(R.id.commentContainer)))
|
||||||
.check(matches(hasDescendant(withId(R.id.comment))))
|
.check(matches(hasDescendant(withId(R.id.comment))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
package com.h.pixeldroid.testUtility
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.test.espresso.Espresso
|
||||||
|
import androidx.test.espresso.UiController
|
||||||
|
import androidx.test.espresso.ViewAction
|
||||||
|
import androidx.test.espresso.action.*
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers
|
||||||
|
import org.hamcrest.BaseMatcher
|
||||||
|
import org.hamcrest.Matcher
|
||||||
|
|
||||||
|
abstract class CustomMatchers {
|
||||||
|
companion object {
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
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)
|
||||||
|
Espresso.onView(matcher).perform(object : ViewAction {
|
||||||
|
override fun getConstraints(): Matcher<View> {
|
||||||
|
return ViewMatchers.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]
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
|
@ -77,6 +77,20 @@ interface PixelfedAPI {
|
||||||
@Field("language") language : String? = null
|
@Field("language") language : String? = null
|
||||||
) : Call<Status>
|
) : Call<Status>
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST("/api/v1/statuses/{id}/reblog")
|
||||||
|
fun reblogStatus(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Path("id") statusId: String,
|
||||||
|
@Field("visibility") visibility: String? = null
|
||||||
|
) : Call<Status>
|
||||||
|
|
||||||
|
@POST("/api/v1/statuses/{id}/unreblog")
|
||||||
|
fun undoReblogStatus(
|
||||||
|
@Path("id") statusId: String,
|
||||||
|
@Header("Authorization") authorization: String
|
||||||
|
) : Call<Status>
|
||||||
|
|
||||||
//Used in our case to retrieve comments for a given status
|
//Used in our case to retrieve comments for a given status
|
||||||
@GET("/api/v1/statuses/{id}/context")
|
@GET("/api/v1/statuses/{id}/context")
|
||||||
fun statusComments(
|
fun statusComments(
|
||||||
|
@ -133,6 +147,12 @@ interface PixelfedAPI {
|
||||||
@Path("id") account_id: String? = null
|
@Path("id") account_id: String? = null
|
||||||
): Call<List<Status>>
|
): Call<List<Status>>
|
||||||
|
|
||||||
|
@GET("/api/v1/accounts/{id}")
|
||||||
|
fun getAccount(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Path("id") accountId : String
|
||||||
|
): Call<Account>
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun create(baseUrl: String): PixelfedAPI {
|
fun create(baseUrl: String): PixelfedAPI {
|
||||||
return Retrofit.Builder()
|
return Retrofit.Builder()
|
||||||
|
|
|
@ -12,9 +12,8 @@ import com.bumptech.glide.Glide
|
||||||
import com.h.pixeldroid.BuildConfig
|
import com.h.pixeldroid.BuildConfig
|
||||||
import com.h.pixeldroid.R
|
import com.h.pixeldroid.R
|
||||||
import com.h.pixeldroid.api.PixelfedAPI
|
import com.h.pixeldroid.api.PixelfedAPI
|
||||||
import com.h.pixeldroid.fragments.feeds.ViewHolder
|
import com.h.pixeldroid.fragments.feeds.PostViewHolder
|
||||||
import com.h.pixeldroid.objects.Status
|
import com.h.pixeldroid.objects.Status
|
||||||
|
|
||||||
import com.h.pixeldroid.objects.Status.Companion.POST_TAG
|
import com.h.pixeldroid.objects.Status.Companion.POST_TAG
|
||||||
import kotlinx.android.synthetic.main.post_fragment.view.*
|
import kotlinx.android.synthetic.main.post_fragment.view.*
|
||||||
|
|
||||||
|
@ -34,15 +33,19 @@ class PostFragment : Fragment() {
|
||||||
status?.setupPost(root, picRequest, root.postPicture, root.profilePic)
|
status?.setupPost(root, picRequest, root.postPicture, root.profilePic)
|
||||||
|
|
||||||
//Setup arguments needed for the onclicklisteners
|
//Setup arguments needed for the onclicklisteners
|
||||||
val holder = ViewHolder(root, requireContext())
|
val holder = PostViewHolder(root, requireContext())
|
||||||
|
|
||||||
val preferences = requireActivity().getSharedPreferences(
|
val preferences = requireActivity().getSharedPreferences(
|
||||||
"${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE
|
"${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE
|
||||||
)
|
)
|
||||||
val accessToken = preferences.getString("accessToken", "")
|
val accessToken = preferences.getString("accessToken", "")
|
||||||
val api = PixelfedAPI.create("${preferences.getString("domain", "")}")
|
val api = PixelfedAPI.create("${preferences.getString("domain", "")}")
|
||||||
|
|
||||||
|
status?.setDescription(root, api, "Bearer $accessToken")
|
||||||
|
|
||||||
//Activate onclickListeners
|
//Activate onclickListeners
|
||||||
status?.activateLiker(holder, api, "Bearer $accessToken")
|
status?.activateLiker(holder, api, "Bearer $accessToken", status.favourited)
|
||||||
|
status?.activateReblogger(holder, api, "Bearer $accessToken", status.reblogged)
|
||||||
status?.activateCommenter(holder, api, "Bearer $accessToken")
|
status?.activateCommenter(holder, api, "Bearer $accessToken")
|
||||||
status?.showComments(holder, api, "Bearer $accessToken")
|
status?.showComments(holder, api, "Bearer $accessToken")
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import androidx.lifecycle.Observer
|
||||||
import androidx.paging.LivePagedListBuilder
|
import androidx.paging.LivePagedListBuilder
|
||||||
import androidx.paging.PagedList
|
import androidx.paging.PagedList
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import at.connyduck.sparkbutton.SparkButton
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.ListPreloader
|
import com.bumptech.glide.ListPreloader
|
||||||
import com.bumptech.glide.RequestBuilder
|
import com.bumptech.glide.RequestBuilder
|
||||||
|
@ -23,7 +24,7 @@ import com.h.pixeldroid.objects.Status
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
|
|
||||||
|
|
||||||
class HomeFragment : FeedFragment<Status, ViewHolder>() {
|
class HomeFragment : FeedFragment<Status, PostViewHolder>() {
|
||||||
|
|
||||||
lateinit var picRequest: RequestBuilder<Drawable>
|
lateinit var picRequest: RequestBuilder<Drawable>
|
||||||
|
|
||||||
|
@ -81,21 +82,21 @@ class HomeFragment : FeedFragment<Status, ViewHolder>() {
|
||||||
/**
|
/**
|
||||||
* [RecyclerView.Adapter] that can display a list of Statuses
|
* [RecyclerView.Adapter] that can display a list of Statuses
|
||||||
*/
|
*/
|
||||||
inner class HomeRecyclerViewAdapter()
|
inner class HomeRecyclerViewAdapter
|
||||||
: FeedsRecyclerViewAdapter<Status, ViewHolder>() {
|
: FeedsRecyclerViewAdapter<Status, PostViewHolder>() {
|
||||||
private val api = pixelfedAPI
|
private val api = pixelfedAPI
|
||||||
private val credential = "Bearer $accessToken"
|
private val credential = "Bearer $accessToken"
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostViewHolder {
|
||||||
val view = LayoutInflater.from(parent.context)
|
val view = LayoutInflater.from(parent.context)
|
||||||
.inflate(R.layout.post_fragment, parent, false)
|
.inflate(R.layout.post_fragment, parent, false)
|
||||||
context = view.context
|
context = view.context
|
||||||
return ViewHolder(view, context)
|
return PostViewHolder(view, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Binds the different elements of the Post Model to the view holder
|
* Binds the different elements of the Post Model to the view holder
|
||||||
*/
|
*/
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: PostViewHolder, position: Int) {
|
||||||
val post = getItem(position) ?: return
|
val post = getItem(position) ?: return
|
||||||
val metrics = context.resources.displayMetrics
|
val metrics = context.resources.displayMetrics
|
||||||
//Limit the height of the different images
|
//Limit the height of the different images
|
||||||
|
@ -105,17 +106,20 @@ class HomeFragment : FeedFragment<Status, ViewHolder>() {
|
||||||
//Setup the post layout
|
//Setup the post layout
|
||||||
post.setupPost(holder.postView, picRequest, holder.postPic, holder.profilePic)
|
post.setupPost(holder.postView, picRequest, holder.postPic, holder.profilePic)
|
||||||
|
|
||||||
//Set initial favorite toggle value
|
//Set the special HTML text
|
||||||
holder.isLiked = post.favourited
|
post.setDescription(holder.postView, api, credential)
|
||||||
|
|
||||||
//Activate liker
|
//Activate liker
|
||||||
post.activateLiker(holder, api, credential)
|
post.activateLiker(holder, api, credential, post.favourited)
|
||||||
|
|
||||||
//Show comments
|
//Show comments
|
||||||
post.showComments(holder, api, credential)
|
post.showComments(holder, api, credential)
|
||||||
|
|
||||||
//Activate Commenter
|
//Activate Commenter
|
||||||
post.activateCommenter(holder, api, credential)
|
post.activateCommenter(holder, api, credential)
|
||||||
|
|
||||||
|
//Activate Reblogger
|
||||||
|
post.activateReblogger(holder, api ,credential, post.reblogged)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPreloadItems(position: Int): MutableList<Status> {
|
override fun getPreloadItems(position: Int): MutableList<Status> {
|
||||||
|
@ -132,7 +136,7 @@ class HomeFragment : FeedFragment<Status, ViewHolder>() {
|
||||||
/**
|
/**
|
||||||
* Represents the posts that will be contained within the feed
|
* Represents the posts that will be contained within the feed
|
||||||
*/
|
*/
|
||||||
class ViewHolder(val postView: View, val context: android.content.Context) : RecyclerView.ViewHolder(postView) {
|
class PostViewHolder(val postView: View, val context: android.content.Context) : RecyclerView.ViewHolder(postView) {
|
||||||
val profilePic : ImageView = postView.findViewById(R.id.profilePic)
|
val profilePic : ImageView = postView.findViewById(R.id.profilePic)
|
||||||
val postPic : ImageView = postView.findViewById(R.id.postPicture)
|
val postPic : ImageView = postView.findViewById(R.id.postPicture)
|
||||||
val username : TextView = postView.findViewById(R.id.username)
|
val username : TextView = postView.findViewById(R.id.username)
|
||||||
|
@ -140,12 +144,15 @@ class ViewHolder(val postView: View, val context: android.content.Context) : Rec
|
||||||
val description : TextView = postView.findViewById(R.id.description)
|
val description : TextView = postView.findViewById(R.id.description)
|
||||||
val nlikes : TextView = postView.findViewById(R.id.nlikes)
|
val nlikes : TextView = postView.findViewById(R.id.nlikes)
|
||||||
val nshares : TextView = postView.findViewById(R.id.nshares)
|
val nshares : TextView = postView.findViewById(R.id.nshares)
|
||||||
val liker : ImageView = postView.findViewById(R.id.liker)
|
|
||||||
|
//Spark buttons
|
||||||
|
val liker : SparkButton = postView.findViewById(R.id.liker)
|
||||||
|
val reblogger : SparkButton = postView.findViewById(R.id.reblogger)
|
||||||
|
|
||||||
val submitCmnt : ImageButton = postView.findViewById(R.id.submitComment)
|
val submitCmnt : ImageButton = postView.findViewById(R.id.submitComment)
|
||||||
val commenter : ImageView = postView.findViewById(R.id.commenter)
|
val commenter : ImageView = postView.findViewById(R.id.commenter)
|
||||||
val comment : EditText = postView.findViewById(R.id.editComment)
|
val comment : EditText = postView.findViewById(R.id.editComment)
|
||||||
val commentCont : LinearLayout = postView.findViewById(R.id.commentContainer)
|
val commentCont : LinearLayout = postView.findViewById(R.id.commentContainer)
|
||||||
val commentIn : LinearLayout = postView.findViewById(R.id.commentIn)
|
val commentIn : LinearLayout = postView.findViewById(R.id.commentIn)
|
||||||
val viewComment : TextView = postView.findViewById(R.id.ViewComments)
|
val viewComment : TextView = postView.findViewById(R.id.ViewComments)
|
||||||
var isLiked : Boolean = false
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import com.h.pixeldroid.R
|
||||||
import com.h.pixeldroid.objects.Account
|
import com.h.pixeldroid.objects.Account
|
||||||
import com.h.pixeldroid.objects.Notification
|
import com.h.pixeldroid.objects.Notification
|
||||||
import com.h.pixeldroid.objects.Status
|
import com.h.pixeldroid.objects.Status
|
||||||
|
import com.h.pixeldroid.utils.HtmlUtils.Companion.parseHTMLText
|
||||||
import kotlinx.android.synthetic.main.fragment_notifications.view.*
|
import kotlinx.android.synthetic.main.fragment_notifications.view.*
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
|
|
||||||
|
@ -145,7 +146,15 @@ class NotificationsFragment : FeedFragment<Notification, NotificationsFragment.N
|
||||||
|
|
||||||
setNotificationType(notification.type, notification.account.username, holder.notificationType)
|
setNotificationType(notification.type, notification.account.username, holder.notificationType)
|
||||||
|
|
||||||
holder.postDescription.text = notification.status?.content ?: ""
|
//Convert HTML to clickable text
|
||||||
|
holder.postDescription.text =
|
||||||
|
parseHTMLText(
|
||||||
|
notification.status?.content ?: "",
|
||||||
|
notification.status?.mentions,
|
||||||
|
pixelfedAPI,
|
||||||
|
context,
|
||||||
|
"Bearer $accessToken"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
with(holder.mView) {
|
with(holder.mView) {
|
||||||
|
@ -167,11 +176,11 @@ class NotificationsFragment : FeedFragment<Notification, NotificationsFragment.N
|
||||||
}
|
}
|
||||||
|
|
||||||
Notification.NotificationType.reblog -> {
|
Notification.NotificationType.reblog -> {
|
||||||
setNotificationTypeTextView(context, R.string.shared_notification, R.drawable.ic_share)
|
setNotificationTypeTextView(context, R.string.shared_notification, R.drawable.ic_reblog_blue)
|
||||||
}
|
}
|
||||||
|
|
||||||
Notification.NotificationType.favourite -> {
|
Notification.NotificationType.favourite -> {
|
||||||
setNotificationTypeTextView(context, R.string.liked_notification, R.drawable.ic_heart)
|
setNotificationTypeTextView(context, R.string.liked_notification, R.drawable.ic_like_full)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
textView.text = format.format(username)
|
textView.text = format.format(username)
|
||||||
|
|
|
@ -3,13 +3,18 @@ package com.h.pixeldroid.objects
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.content.ContextCompat.startActivity
|
import androidx.core.content.ContextCompat.startActivity
|
||||||
import com.h.pixeldroid.ProfileActivity
|
import com.h.pixeldroid.ProfileActivity
|
||||||
import com.h.pixeldroid.R
|
import com.h.pixeldroid.R
|
||||||
|
import com.h.pixeldroid.api.PixelfedAPI
|
||||||
import com.h.pixeldroid.utils.ImageConverter
|
import com.h.pixeldroid.utils.ImageConverter
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.Callback
|
||||||
|
import retrofit2.Response
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -18,65 +23,92 @@ https://docs.joinmastodon.org/entities/account/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
data class Account(
|
data class Account(
|
||||||
//Base attributes
|
//Base attributes
|
||||||
val id: String,
|
val id: String,
|
||||||
val username: String,
|
val username: String,
|
||||||
val acct: String,
|
val acct: String,
|
||||||
val url: String, //HTTPS URL
|
val url: String, //HTTPS URL
|
||||||
//Display attributes
|
//Display attributes
|
||||||
val display_name: String,
|
val display_name: String,
|
||||||
val note: String, //HTML
|
val note: String, //HTML
|
||||||
val avatar: String, //URL
|
val avatar: String, //URL
|
||||||
val avatar_static: String, //URL
|
val avatar_static: String, //URL
|
||||||
val header: String, //URL
|
val header: String, //URL
|
||||||
val header_static: String, //URL
|
val header_static: String, //URL
|
||||||
val locked: Boolean,
|
val locked: Boolean,
|
||||||
val emojis: List<Emoji>,
|
val emojis: List<Emoji>,
|
||||||
val discoverable: Boolean,
|
val discoverable: Boolean,
|
||||||
//Statistical attributes
|
//Statistical attributes
|
||||||
val created_at: String, //ISO 8601 Datetime (maybe can use a date type)
|
val created_at: String, //ISO 8601 Datetime (maybe can use a date type)
|
||||||
val statuses_count: Int,
|
val statuses_count: Int,
|
||||||
val followers_count: Int,
|
val followers_count: Int,
|
||||||
val following_count: Int,
|
val following_count: Int,
|
||||||
//Optional attributes
|
//Optional attributes
|
||||||
val moved: Account? = null,
|
val moved: Account? = null,
|
||||||
val fields: List<Field>? = emptyList(),
|
val fields: List<Field>? = emptyList(),
|
||||||
val bot: Boolean = false,
|
val bot: Boolean = false,
|
||||||
val source: Source? = null
|
val source: Source? = null
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
companion object {
|
companion object {
|
||||||
const val ACCOUNT_TAG = "AccountTag"
|
const val ACCOUNT_TAG = "AccountTag"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Opens an activity of the profile withn the given id
|
||||||
|
*/
|
||||||
|
fun getAccountFromId(id: String, api : PixelfedAPI, context: Context, credential: String) {
|
||||||
|
Log.e("ACCOUNT_ID", id)
|
||||||
|
api.getAccount(credential, id).enqueue( object : Callback<Account> {
|
||||||
|
override fun onFailure(call: Call<Account>, t: Throwable) {
|
||||||
|
Log.e("GET ACCOUNT ERROR", t.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResponse(
|
||||||
|
call: Call<Account>,
|
||||||
|
response: Response<Account>
|
||||||
|
) {
|
||||||
|
if(response.code() == 200) {
|
||||||
|
val account = response.body()!!
|
||||||
|
|
||||||
|
//Open the account page in a seperate activity
|
||||||
|
account.openProfile(context)
|
||||||
|
} else {
|
||||||
|
Log.e("ERROR CODE", response.code().toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Open profile activity with given account
|
// Open profile activity with given account
|
||||||
fun openProfile(context: Context) {
|
fun openProfile(context: Context) {
|
||||||
val intent = Intent(context, ProfileActivity::class.java)
|
val intent = Intent(context, ProfileActivity::class.java)
|
||||||
intent.putExtra(Account.ACCOUNT_TAG, this)
|
intent.putExtra(Account.ACCOUNT_TAG, this)
|
||||||
startActivity(context, intent, null)
|
startActivity(context, intent, null)
|
||||||
}
|
}
|
||||||
// Populate myProfile page with user's data
|
// Populate myProfile page with user's data
|
||||||
fun setContent(view: View) {
|
fun setContent(view: View) {
|
||||||
val profilePicture = view.findViewById<ImageView>(R.id.profilePictureImageView)
|
val profilePicture = view.findViewById<ImageView>(R.id.profilePictureImageView)
|
||||||
ImageConverter.setRoundImageFromURL(view, this.avatar, profilePicture)
|
ImageConverter.setRoundImageFromURL(view, this.avatar, profilePicture)
|
||||||
|
|
||||||
val description = view.findViewById<TextView>(R.id.descriptionTextView)
|
val description = view.findViewById<TextView>(R.id.descriptionTextView)
|
||||||
description.text = this.note
|
description.text = this.note
|
||||||
|
|
||||||
val accountName = view.findViewById<TextView>(R.id.accountNameTextView)
|
val accountName = view.findViewById<TextView>(R.id.accountNameTextView)
|
||||||
accountName.text = this.username
|
accountName.text = this.username
|
||||||
accountName.setTypeface(null, Typeface.BOLD)
|
accountName.setTypeface(null, Typeface.BOLD)
|
||||||
|
|
||||||
val nbPosts = view.findViewById<TextView>(R.id.nbPostsTextView)
|
val nbPosts = view.findViewById<TextView>(R.id.nbPostsTextView)
|
||||||
nbPosts.text = "${this.statuses_count}\nPosts"
|
nbPosts.text = "${this.statuses_count}\nPosts"
|
||||||
nbPosts.setTypeface(null, Typeface.BOLD)
|
nbPosts.setTypeface(null, Typeface.BOLD)
|
||||||
|
|
||||||
val nbFollowers = view.findViewById<TextView>(R.id.nbFollowersTextView)
|
val nbFollowers = view.findViewById<TextView>(R.id.nbFollowersTextView)
|
||||||
nbFollowers.text = "${this.followers_count}\nFollowers"
|
nbFollowers.text = "${this.followers_count}\nFollowers"
|
||||||
nbFollowers.setTypeface(null, Typeface.BOLD)
|
nbFollowers.setTypeface(null, Typeface.BOLD)
|
||||||
|
|
||||||
val nbFollowing = view.findViewById<TextView>(R.id.nbFollowingTextView)
|
val nbFollowing = view.findViewById<TextView>(R.id.nbFollowingTextView)
|
||||||
nbFollowing.text = "${this.following_count}\nFollowing"
|
nbFollowing.text = "${this.following_count}\nFollowing"
|
||||||
nbFollowing.setTypeface(null, Typeface.BOLD)
|
nbFollowing.setTypeface(null, Typeface.BOLD)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,5 +2,10 @@ package com.h.pixeldroid.objects
|
||||||
|
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
class Mention : Serializable {
|
data class Mention(
|
||||||
}
|
//Mentioned user
|
||||||
|
val id: String,
|
||||||
|
val username : String,
|
||||||
|
val acct : String, //URI of mentioned user (username if local, else username@domain)
|
||||||
|
val url : String //URL of mentioned user's profile
|
||||||
|
) : Serializable
|
|
@ -1,22 +1,32 @@
|
||||||
package com.h.pixeldroid.objects
|
package com.h.pixeldroid.objects
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.os.Build
|
||||||
|
import android.text.Html
|
||||||
|
import android.text.Spanned
|
||||||
|
import android.text.method.LinkMovementMethod
|
||||||
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.core.text.toSpanned
|
||||||
import com.bumptech.glide.RequestBuilder
|
import com.bumptech.glide.RequestBuilder
|
||||||
import com.h.pixeldroid.R
|
import com.h.pixeldroid.R
|
||||||
import com.h.pixeldroid.api.PixelfedAPI
|
import com.h.pixeldroid.api.PixelfedAPI
|
||||||
import com.h.pixeldroid.fragments.feeds.ViewHolder
|
import com.h.pixeldroid.fragments.feeds.PostViewHolder
|
||||||
|
import com.h.pixeldroid.utils.HtmlUtils.Companion.parseHTMLText
|
||||||
import com.h.pixeldroid.utils.ImageConverter
|
import com.h.pixeldroid.utils.ImageConverter
|
||||||
import com.h.pixeldroid.utils.PostUtils.Companion.likePostCall
|
import com.h.pixeldroid.utils.PostUtils.Companion.likePostCall
|
||||||
import com.h.pixeldroid.utils.PostUtils.Companion.postComment
|
import com.h.pixeldroid.utils.PostUtils.Companion.postComment
|
||||||
|
import com.h.pixeldroid.utils.PostUtils.Companion.reblogPost
|
||||||
import com.h.pixeldroid.utils.PostUtils.Companion.retrieveComments
|
import com.h.pixeldroid.utils.PostUtils.Companion.retrieveComments
|
||||||
import com.h.pixeldroid.utils.PostUtils.Companion.toggleCommentInput
|
import com.h.pixeldroid.utils.PostUtils.Companion.toggleCommentInput
|
||||||
import com.h.pixeldroid.utils.PostUtils.Companion.unLikePostCall
|
import com.h.pixeldroid.utils.PostUtils.Companion.unLikePostCall
|
||||||
|
import com.h.pixeldroid.utils.PostUtils.Companion.undoReblogPost
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -70,17 +80,21 @@ data class Status(
|
||||||
fun getProfilePicUrl() : String? = account.avatar
|
fun getProfilePicUrl() : String? = account.avatar
|
||||||
fun getPostPreviewURL() : String? = media_attachments?.getOrNull(0)?.preview_url
|
fun getPostPreviewURL() : String? = media_attachments?.getOrNull(0)?.preview_url
|
||||||
|
|
||||||
fun getDescription() : CharSequence {
|
/**
|
||||||
val description = content as CharSequence
|
* @brief returns the parsed version of the HTML description
|
||||||
|
*/
|
||||||
|
private fun getDescription(api: PixelfedAPI, context: Context, credential: String) : Spanned {
|
||||||
|
val description = content
|
||||||
if(description.isEmpty()) {
|
if(description.isEmpty()) {
|
||||||
return "No description"
|
return "No description".toSpanned()
|
||||||
}
|
}
|
||||||
return description
|
return parseHTMLText(description, mentions, api, context, credential)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUsername() : CharSequence {
|
fun getUsername() : CharSequence {
|
||||||
var name = account?.display_name
|
var name = account?.display_name
|
||||||
if (name.isNullOrEmpty()) {
|
if (name.isEmpty()) {
|
||||||
name = account?.username
|
name = account?.username
|
||||||
}
|
}
|
||||||
return name!!
|
return name!!
|
||||||
|
@ -112,8 +126,6 @@ data class Status(
|
||||||
usernameDesc.text = this.getUsername()
|
usernameDesc.text = this.getUsername()
|
||||||
usernameDesc.setTypeface(null, Typeface.BOLD)
|
usernameDesc.setTypeface(null, Typeface.BOLD)
|
||||||
|
|
||||||
rootView.findViewById<TextView>(R.id.description).text = this.getDescription()
|
|
||||||
|
|
||||||
val nlikes = rootView.findViewById<TextView>(R.id.nlikes)
|
val nlikes = rootView.findViewById<TextView>(R.id.nlikes)
|
||||||
nlikes.text = this.getNLikes()
|
nlikes.text = this.getNLikes()
|
||||||
nlikes.setTypeface(null, Typeface.BOLD)
|
nlikes.setTypeface(null, Typeface.BOLD)
|
||||||
|
@ -135,25 +147,60 @@ data class Status(
|
||||||
rootView.findViewById<LinearLayout>(R.id.commentIn).visibility = View.GONE
|
rootView.findViewById<LinearLayout>(R.id.commentIn).visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
fun activateLiker(
|
fun setDescription(rootView: View, api : PixelfedAPI, credential: String) {
|
||||||
holder : ViewHolder,
|
val desc = rootView.findViewById<TextView>(R.id.description)
|
||||||
api: PixelfedAPI,
|
|
||||||
credential: String
|
desc.text = this.getDescription(api, rootView.context, credential)
|
||||||
|
desc.movementMethod = LinkMovementMethod.getInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun activateReblogger(
|
||||||
|
holder : PostViewHolder,
|
||||||
|
api : PixelfedAPI,
|
||||||
|
credential: String,
|
||||||
|
isReblogged : Boolean
|
||||||
) {
|
) {
|
||||||
//Activate the liker
|
//Set initial button state
|
||||||
holder.liker.setOnClickListener {
|
holder.reblogger.isChecked = isReblogged
|
||||||
if (holder.isLiked) {
|
|
||||||
//Unlike the post
|
//Activate the button
|
||||||
unLikePostCall(holder, api, credential, this)
|
holder.reblogger.setEventListener { _, buttonState ->
|
||||||
|
if (buttonState) {
|
||||||
|
Log.e("REBLOG", "Reblogged post")
|
||||||
|
// Button is active
|
||||||
|
reblogPost(holder, api, credential, this)
|
||||||
} else {
|
} else {
|
||||||
//like the post
|
Log.e("REBLOG", "Undo Reblogged post")
|
||||||
likePostCall(holder, api, credential, this)
|
// Button is inactive
|
||||||
|
undoReblogPost(holder, api, credential, this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun activateLiker(
|
||||||
|
holder : PostViewHolder,
|
||||||
|
api: PixelfedAPI,
|
||||||
|
credential: String,
|
||||||
|
isLiked: Boolean
|
||||||
|
) {
|
||||||
|
//Set initial state
|
||||||
|
holder.liker.isChecked = isLiked
|
||||||
|
|
||||||
|
//Activate the liker
|
||||||
|
holder.liker.setEventListener { _, buttonState ->
|
||||||
|
if (buttonState) {
|
||||||
|
// Button is active
|
||||||
|
likePostCall(holder, api, credential, this)
|
||||||
|
} else {
|
||||||
|
// Button is inactive
|
||||||
|
unLikePostCall(holder, api, credential, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun showComments(
|
fun showComments(
|
||||||
holder : ViewHolder,
|
holder : PostViewHolder,
|
||||||
api: PixelfedAPI,
|
api: PixelfedAPI,
|
||||||
credential: String
|
credential: String
|
||||||
) {
|
) {
|
||||||
|
@ -172,7 +219,7 @@ data class Status(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun activateCommenter(
|
fun activateCommenter(
|
||||||
holder : ViewHolder,
|
holder : PostViewHolder,
|
||||||
api: PixelfedAPI,
|
api: PixelfedAPI,
|
||||||
credential: String
|
credential: String
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
package com.h.pixeldroid.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import android.text.Html
|
||||||
|
import android.text.SpannableStringBuilder
|
||||||
|
import android.text.Spanned
|
||||||
|
import android.text.style.ClickableSpan
|
||||||
|
import android.text.style.URLSpan
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.text.toSpanned
|
||||||
|
import com.h.pixeldroid.api.PixelfedAPI
|
||||||
|
import com.h.pixeldroid.objects.Account.Companion.getAccountFromId
|
||||||
|
import com.h.pixeldroid.objects.Mention
|
||||||
|
import com.h.pixeldroid.utils.customSpans.ClickableSpanNoUnderline
|
||||||
|
import java.net.URI
|
||||||
|
import java.net.URISyntaxException
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
|
class HtmlUtils {
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private fun fromHtml(html: String): Spanned {
|
||||||
|
val result: Spanned = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY)
|
||||||
|
} else {
|
||||||
|
Html.fromHtml(html)
|
||||||
|
}
|
||||||
|
return result.trim().toSpanned()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDomain(urlString: String?): String {
|
||||||
|
val uri: URI
|
||||||
|
try {
|
||||||
|
uri = URI(urlString!!)
|
||||||
|
} catch (e: URISyntaxException) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
val host: String = uri.host
|
||||||
|
return if (host.startsWith("www.")) {
|
||||||
|
host.substring(4)
|
||||||
|
} else {
|
||||||
|
host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseHTMLText(
|
||||||
|
text : String,
|
||||||
|
mentions: List<Mention>?,
|
||||||
|
api : PixelfedAPI,
|
||||||
|
context: Context,
|
||||||
|
credential: String
|
||||||
|
) : Spanned {
|
||||||
|
//Convert text to spannable
|
||||||
|
val content = fromHtml(text)
|
||||||
|
|
||||||
|
//Retrive all links that should be made clickable
|
||||||
|
val builder = SpannableStringBuilder(content)
|
||||||
|
val urlSpans = content.getSpans(0, content.length, URLSpan::class.java)
|
||||||
|
|
||||||
|
for(span in urlSpans) {
|
||||||
|
val start = builder.getSpanStart(span)
|
||||||
|
val end = builder.getSpanEnd(span)
|
||||||
|
val flags = builder.getSpanFlags(span)
|
||||||
|
val text = builder.subSequence(start, end)
|
||||||
|
var customSpan: ClickableSpan? = null
|
||||||
|
|
||||||
|
//Handle hashtags
|
||||||
|
if (text[0] == '#') {
|
||||||
|
val tag = text.subSequence(1, text.length).toString()
|
||||||
|
customSpan = object : ClickableSpanNoUnderline() {
|
||||||
|
override fun onClick(widget: View) {
|
||||||
|
Toast.makeText(context, tag, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Handle mentions
|
||||||
|
if(text[0] == '@' && !mentions.isNullOrEmpty()) {
|
||||||
|
val accountUsername = text.subSequence(1, text.length).toString()
|
||||||
|
var id: String? = null
|
||||||
|
|
||||||
|
//Go through all mentions stored in the status
|
||||||
|
for (mention in mentions) {
|
||||||
|
if (mention.username.toLowerCase(Locale.ROOT)
|
||||||
|
== accountUsername.toLowerCase(Locale.ROOT)
|
||||||
|
) {
|
||||||
|
id = mention.id
|
||||||
|
|
||||||
|
//Mentions can be of users in other domains
|
||||||
|
if (mention.url.contains(getDomain(span.url))) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check that we found a user for the given mention
|
||||||
|
if (id != null) {
|
||||||
|
val accountId: String = id
|
||||||
|
customSpan = object : ClickableSpanNoUnderline() {
|
||||||
|
override fun onClick(widget: View) {
|
||||||
|
Log.e("MENTION", "CLICKED")
|
||||||
|
//Retrieve the account for the given profile
|
||||||
|
getAccountFromId(accountId, api, context, credential)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.removeSpan(span);
|
||||||
|
builder.setSpan(customSpan, start, end, flags);
|
||||||
|
|
||||||
|
// Add zero-width space after links in end of line to fix its too large hitbox.
|
||||||
|
if (end >= builder.length || builder.subSequence(end, end + 1).toString() == "\n") {
|
||||||
|
builder.insert(end, "\u200B")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,49 +1,110 @@
|
||||||
package com.h.pixeldroid.utils
|
package com.h.pixeldroid.utils
|
||||||
|
|
||||||
import android.graphics.Typeface
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.cardview.widget.CardView
|
|
||||||
import com.h.pixeldroid.R
|
import com.h.pixeldroid.R
|
||||||
import com.h.pixeldroid.api.PixelfedAPI
|
import com.h.pixeldroid.api.PixelfedAPI
|
||||||
import com.h.pixeldroid.fragments.feeds.ViewHolder
|
import com.h.pixeldroid.fragments.feeds.PostViewHolder
|
||||||
import com.h.pixeldroid.objects.Account
|
|
||||||
import com.h.pixeldroid.objects.Context
|
import com.h.pixeldroid.objects.Context
|
||||||
import com.h.pixeldroid.objects.Status
|
import com.h.pixeldroid.objects.Status
|
||||||
|
import com.h.pixeldroid.utils.ImageConverter.Companion.setImageFromDrawable
|
||||||
import kotlinx.android.synthetic.main.comment.view.*
|
import kotlinx.android.synthetic.main.comment.view.*
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.Callback
|
import retrofit2.Callback
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
|
||||||
class PostUtils {
|
abstract class PostUtils {
|
||||||
companion object {
|
companion object {
|
||||||
fun toggleCommentInput(
|
fun toggleCommentInput(
|
||||||
holder : ViewHolder
|
holder : PostViewHolder
|
||||||
) {
|
) {
|
||||||
//Toggle comment button
|
//Toggle comment button
|
||||||
holder.commenter.setOnClickListener {
|
holder.commenter.setOnClickListener {
|
||||||
when(holder.commentIn.visibility) {
|
when(holder.commentIn.visibility) {
|
||||||
View.VISIBLE -> holder.commentIn.visibility = View.GONE
|
View.VISIBLE -> {
|
||||||
View.INVISIBLE -> holder.commentIn.visibility = View.VISIBLE
|
holder.commentIn.visibility = View.GONE
|
||||||
View.GONE -> holder.commentIn.visibility = View.VISIBLE
|
setImageFromDrawable(holder.postView, holder.commenter, R.drawable.ic_comment_empty)
|
||||||
|
}
|
||||||
|
View.GONE -> {
|
||||||
|
holder.commentIn.visibility = View.VISIBLE
|
||||||
|
setImageFromDrawable(holder.postView, holder.commenter, R.drawable.ic_comment_blue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun likePostCall(
|
fun reblogPost(
|
||||||
holder : ViewHolder,
|
holder : PostViewHolder,
|
||||||
api: PixelfedAPI,
|
api: PixelfedAPI,
|
||||||
credential: String,
|
credential: String,
|
||||||
post : Status
|
post : Status
|
||||||
) {
|
) {
|
||||||
|
//Call the api function
|
||||||
|
api.reblogStatus(credential, post.id).enqueue(object : Callback<Status> {
|
||||||
|
override fun onFailure(call: Call<Status>, t: Throwable) {
|
||||||
|
Log.e("REBLOG ERROR", t.toString())
|
||||||
|
holder.reblogger.isChecked = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResponse(call: Call<Status>, response: Response<Status>) {
|
||||||
|
if(response.code() == 200) {
|
||||||
|
val resp = response.body()!!
|
||||||
|
|
||||||
|
//Update shown share count
|
||||||
|
holder.nshares.text = resp.getNShares()
|
||||||
|
holder.reblogger.isChecked = resp.reblogged
|
||||||
|
} else {
|
||||||
|
Log.e("RESPONSE_CODE", response.code().toString())
|
||||||
|
holder.reblogger.isChecked = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun undoReblogPost(
|
||||||
|
holder : PostViewHolder,
|
||||||
|
api: PixelfedAPI,
|
||||||
|
credential: String,
|
||||||
|
post : Status
|
||||||
|
) {
|
||||||
|
//Call the api function
|
||||||
|
api.undoReblogStatus(credential, post.id).enqueue(object : Callback<Status> {
|
||||||
|
override fun onFailure(call: Call<Status>, t: Throwable) {
|
||||||
|
Log.e("REBLOG ERROR", t.toString())
|
||||||
|
holder.reblogger.isChecked = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResponse(call: Call<Status>, response: Response<Status>) {
|
||||||
|
if(response.code() == 200) {
|
||||||
|
val resp = response.body()!!
|
||||||
|
|
||||||
|
//Update shown share count
|
||||||
|
holder.nshares.text = resp.getNShares()
|
||||||
|
holder.reblogger.isChecked = resp.reblogged
|
||||||
|
} else {
|
||||||
|
Log.e("RESPONSE_CODE", response.code().toString())
|
||||||
|
holder.reblogger.isChecked = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun likePostCall(
|
||||||
|
holder : PostViewHolder,
|
||||||
|
api: PixelfedAPI,
|
||||||
|
credential: String,
|
||||||
|
post : Status
|
||||||
|
) {
|
||||||
|
//Call the api function
|
||||||
api.likePost(credential, post.id).enqueue(object : Callback<Status> {
|
api.likePost(credential, post.id).enqueue(object : Callback<Status> {
|
||||||
override fun onFailure(call: Call<Status>, t: Throwable) {
|
override fun onFailure(call: Call<Status>, t: Throwable) {
|
||||||
Log.e("LIKE ERROR", t.toString())
|
Log.e("LIKE ERROR", t.toString())
|
||||||
|
holder.liker.isChecked = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResponse(call: Call<Status>, response: Response<Status>) {
|
override fun onResponse(call: Call<Status>, response: Response<Status>) {
|
||||||
|
@ -52,9 +113,10 @@ class PostUtils {
|
||||||
|
|
||||||
//Update shown like count and internal like toggle
|
//Update shown like count and internal like toggle
|
||||||
holder.nlikes.text = resp.getNLikes()
|
holder.nlikes.text = resp.getNLikes()
|
||||||
holder.isLiked = resp.favourited
|
holder.liker.isChecked = resp.favourited
|
||||||
} else {
|
} else {
|
||||||
Log.e("RESPOSE_CODE", response.code().toString())
|
Log.e("RESPONSE_CODE", response.code().toString())
|
||||||
|
holder.liker.isChecked = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,14 +124,16 @@ class PostUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unLikePostCall(
|
fun unLikePostCall(
|
||||||
holder : ViewHolder,
|
holder : PostViewHolder,
|
||||||
api: PixelfedAPI,
|
api: PixelfedAPI,
|
||||||
credential: String,
|
credential: String,
|
||||||
post : Status
|
post : Status
|
||||||
) {
|
) {
|
||||||
|
//Call the api function
|
||||||
api.unlikePost(credential, post.id).enqueue(object : Callback<Status> {
|
api.unlikePost(credential, post.id).enqueue(object : Callback<Status> {
|
||||||
override fun onFailure(call: Call<Status>, t: Throwable) {
|
override fun onFailure(call: Call<Status>, t: Throwable) {
|
||||||
Log.e("UNLIKE ERROR", t.toString())
|
Log.e("UNLIKE ERROR", t.toString())
|
||||||
|
holder.liker.isChecked = true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResponse(call: Call<Status>, response: Response<Status>) {
|
override fun onResponse(call: Call<Status>, response: Response<Status>) {
|
||||||
|
@ -78,9 +142,10 @@ class PostUtils {
|
||||||
|
|
||||||
//Update shown like count and internal like toggle
|
//Update shown like count and internal like toggle
|
||||||
holder.nlikes.text = resp.getNLikes()
|
holder.nlikes.text = resp.getNLikes()
|
||||||
holder.isLiked = resp.favourited
|
holder.liker.isChecked = resp.favourited
|
||||||
} else {
|
} else {
|
||||||
Log.e("RESPOSE_CODE", response.code().toString())
|
Log.e("RESPONSE_CODE", response.code().toString())
|
||||||
|
holder.liker.isChecked = true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -89,7 +154,7 @@ class PostUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun postComment(
|
fun postComment(
|
||||||
holder : ViewHolder,
|
holder : PostViewHolder,
|
||||||
api: PixelfedAPI,
|
api: PixelfedAPI,
|
||||||
credential: String,
|
credential: String,
|
||||||
post : Status
|
post : Status
|
||||||
|
@ -131,7 +196,7 @@ class PostUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun retrieveComments(
|
fun retrieveComments(
|
||||||
holder : ViewHolder,
|
holder : PostViewHolder,
|
||||||
api: PixelfedAPI,
|
api: PixelfedAPI,
|
||||||
credential: String,
|
credential: String,
|
||||||
post : Status
|
post : Status
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.h.pixeldroid.utils.customSpans
|
||||||
|
|
||||||
|
import android.text.TextPaint
|
||||||
|
import android.text.style.ClickableSpan
|
||||||
|
|
||||||
|
abstract class ClickableSpanNoUnderline : ClickableSpan() {
|
||||||
|
override fun updateDrawState(ds: TextPaint) {
|
||||||
|
super.updateDrawState(ds)
|
||||||
|
ds.isUnderlineText = false
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="24dp" android:tint="#1737A2"
|
||||||
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M20,2H4c-1.1,0 -2,0.9 -2,2v18l4,-4h14c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M20,2L4,2c-1.1,0 -2,0.9 -2,2v18l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM20,16L6,16l-2,2L4,4h16v12z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M20,2H4c-1.1,0 -2,0.9 -2,2v18l4,-4h14c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<vector android:height="21.6dp" android:viewportHeight="99"
|
||||||
|
android:viewportWidth="110" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#00000000"
|
||||||
|
android:pathData="M54.5,94.5C58.2,90.74 79.39,69.25 92.63,55.83C104.5,43.8 104.24,26.26 93.46,15.39C82.67,4.5 65.23,4.55 54.5,15.48C43.76,4.55 26.32,4.5 15.54,15.38C4.75,26.25 4.5,43.79 16.36,55.83C29.6,69.25 50.79,90.74 54.5,94.5Z"
|
||||||
|
android:strokeColor="#000000" android:strokeWidth="9"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="21.6dp" android:viewportHeight="99"
|
||||||
|
android:viewportWidth="110" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#c42f0a"
|
||||||
|
android:pathData="M54.5,94.5C58.2,90.74 79.39,69.25 92.63,55.83C104.5,43.8 104.24,26.26 93.46,15.39C82.67,4.5 65.23,4.55 54.5,15.48C43.76,4.55 26.32,4.5 15.54,15.38C4.75,26.25 4.5,43.79 16.36,55.83C29.6,69.25 50.79,90.74 54.5,94.5Z" android:strokeColor="#00000000"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,15 @@
|
||||||
|
<vector android:height="24dp" android:viewportHeight="85"
|
||||||
|
android:viewportWidth="111" android:width="31.341177dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#00000000"
|
||||||
|
android:pathData="M30.5,64.5L90.5,64.5L90.5,39.81"
|
||||||
|
android:strokeColor="#000000" android:strokeWidth="9"/>
|
||||||
|
<path android:fillColor="#000000"
|
||||||
|
android:pathData="M90.5,25.56L100,44.56L90.5,39.81L81,44.56Z"
|
||||||
|
android:strokeColor="#000000" android:strokeWidth="9"/>
|
||||||
|
<path android:fillColor="#00000000"
|
||||||
|
android:pathData="M80.5,18.5L20.5,18.5L20.5,44.19"
|
||||||
|
android:strokeColor="#000000" android:strokeWidth="9"/>
|
||||||
|
<path android:fillColor="#000000"
|
||||||
|
android:pathData="M20.5,58.44L11,39.44L20.5,44.19L30,39.44Z"
|
||||||
|
android:strokeColor="#000000" android:strokeWidth="9"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,15 @@
|
||||||
|
<vector android:height="24dp" android:viewportHeight="85"
|
||||||
|
android:viewportWidth="111" android:width="31.341177dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#00000000"
|
||||||
|
android:pathData="M30.5,64.5L90.5,64.5L90.5,39.81"
|
||||||
|
android:strokeColor="#0000ff" android:strokeWidth="9"/>
|
||||||
|
<path android:fillColor="#0000ff"
|
||||||
|
android:pathData="M90.5,25.56L100,44.56L90.5,39.81L81,44.56Z"
|
||||||
|
android:strokeColor="#0000ff" android:strokeWidth="9"/>
|
||||||
|
<path android:fillColor="#00000000"
|
||||||
|
android:pathData="M80.5,18.5L20.5,18.5L20.5,44.19"
|
||||||
|
android:strokeColor="#0000ff" android:strokeWidth="9"/>
|
||||||
|
<path android:fillColor="#0000ff"
|
||||||
|
android:pathData="M20.5,58.44L11,39.44L20.5,44.19L30,39.44Z"
|
||||||
|
android:strokeColor="#0000ff" android:strokeWidth="9"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="24dp" android:tint="#1737A2"
|
||||||
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
|
||||||
|
</vector>
|
|
@ -1,4 +0,0 @@
|
||||||
<vector android:height="19.2dp" android:viewportHeight="512"
|
|
||||||
android:viewportWidth="640" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<path android:fillColor="#FF000000" android:pathData="M629.657,343.598L528.971,444.284c-9.373,9.372 -24.568,9.372 -33.941,0L394.343,343.598c-9.373,-9.373 -9.373,-24.569 0,-33.941l10.823,-10.823c9.562,-9.562 25.133,-9.34 34.419,0.492L480,342.118L480,160L292.451,160a24.005,24.005 0,0 1,-16.971 -7.029l-16,-16C244.361,121.851 255.069,96 276.451,96L520,96c13.255,0 24,10.745 24,24v222.118l40.416,-42.792c9.285,-9.831 24.856,-10.054 34.419,-0.492l10.823,10.823c9.372,9.372 9.372,24.569 -0.001,33.941zM364.519,359.029A23.999,23.999 0,0 0,347.548 352L160,352L160,169.881l40.416,42.792c9.286,9.831 24.856,10.054 34.419,0.491l10.822,-10.822c9.373,-9.373 9.373,-24.569 0,-33.941L144.971,67.716c-9.373,-9.373 -24.569,-9.373 -33.941,0L10.343,168.402c-9.373,9.373 -9.373,24.569 0,33.941l10.822,10.822c9.562,9.562 25.133,9.34 34.419,-0.491L96,169.881L96,392c0,13.255 10.745,24 24,24h243.549c21.382,0 32.09,-25.851 16.971,-40.971l-16.001,-16z"/>
|
|
||||||
</vector>
|
|
|
@ -1,200 +1,195 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
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_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="wrap_content"
|
||||||
<androidx.cardview.widget.CardView
|
xmlns:sparkbutton="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_marginTop="5dp"
|
tools:context=".fragments.PostFragment">
|
||||||
android:layout_marginBottom="5dp"
|
<androidx.cardview.widget.CardView
|
||||||
android:layout_width="match_parent"
|
android:layout_marginTop="5dp"
|
||||||
android:layout_height="wrap_content">
|
android:layout_marginBottom="5dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false">
|
||||||
|
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/profilePic"
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
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="@+id/profilePic"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/profilePic"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/profilePic"
|
||||||
|
tools:text="Account" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/postPicture"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:layout_marginTop="10dp"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/profilePic"
|
||||||
|
tools:src="@color/browser_actions_bg_grey"/>
|
||||||
|
|
||||||
<LinearLayout
|
<ImageView
|
||||||
android:id="@+id/linearLayout3"
|
android:id="@+id/commenter"
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:padding="4dp"
|
||||||
|
android:src="@drawable/ic_comment_empty"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/liker"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/reblogger"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/liker"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/liker"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<at.connyduck.sparkbutton.SparkButton
|
||||||
|
android:id="@+id/liker"
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:padding="4dp"
|
||||||
|
sparkbutton:activeImage="@drawable/ic_like_full"
|
||||||
|
sparkbutton:iconSize="28dp"
|
||||||
|
sparkbutton:inactiveImage="@drawable/ic_like_empty"
|
||||||
|
sparkbutton:primaryColor="@color/heart_red"
|
||||||
|
sparkbutton:secondaryColor="@color/black"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/commenter"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="spread"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/profilePic"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/postPicture"/>
|
||||||
|
|
||||||
|
<at.connyduck.sparkbutton.SparkButton
|
||||||
|
android:id="@+id/reblogger"
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:padding="4dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/commenter"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/commenter"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/commenter"
|
||||||
|
sparkbutton:activeImage="@drawable/ic_reblog_blue"
|
||||||
|
sparkbutton:iconSize="28dp"
|
||||||
|
sparkbutton:inactiveImage="@drawable/ic_reblog"
|
||||||
|
sparkbutton:primaryColor="@color/share_blue"
|
||||||
|
sparkbutton:secondaryColor="@color/black"/>
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/nlikes"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="50"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/liker"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/liker"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/liker"
|
||||||
|
tools:text="2 Likes" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/nshares"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="50"
|
||||||
|
android:gravity="right"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/reblogger"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/reblogger"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/reblogger"
|
||||||
|
tools:text="3 Shares" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/usernameDesc"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/profilePic"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/nlikes"
|
||||||
|
tools:text="Account"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/description"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hyphenationFrequency="full"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/usernameDesc"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/usernameDesc"
|
||||||
|
tools:text="This is a description, describing stuff.\nIt contains multiple lines, and that's okay. It's also got some really long lines, and we love it for it." />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/commentIn"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/description"
|
||||||
|
tools:layout_editor_absoluteX="10dp">
|
||||||
|
|
||||||
|
<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"
|
||||||
|
android:importantForAutofill="no"
|
||||||
|
android:inputType="text" />
|
||||||
|
</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:layout_weight="1"
|
||||||
|
android:contentDescription="Submit button"
|
||||||
|
android:src="@drawable/ic_send_blue" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/commentContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:layout_editor_absoluteY="315dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/commentIn">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/ViewComments"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:layout_marginStart="10dp"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
android:layout_marginBottom="10dp"
|
||||||
|
tools:text="TextView"/>
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
</LinearLayout>
|
||||||
android:id="@+id/constraintLayout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="10dp">
|
|
||||||
|
|
||||||
<ImageView
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
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
|
</androidx.cardview.widget.CardView>
|
||||||
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.cardview.widget.CardView>
|
|
||||||
|
|
||||||
</ScrollView>
|
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
|
@ -1,4 +1,15 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="auth_scheme" translatable="false">oauth2redirect</string>
|
<string name="auth_scheme" translatable="false">oauth2redirect</string>
|
||||||
|
<declare-styleable name="SparkButton">
|
||||||
|
<attr format="dimension|reference" name="iconSize"/>
|
||||||
|
<attr format="reference" name="activeImage"/>
|
||||||
|
<attr format="reference" name="inactiveImage"/>
|
||||||
|
<attr format="reference" name="primaryColor"/>
|
||||||
|
<attr format="reference" name="secondaryColor"/>
|
||||||
|
<attr format="float" name="animationSpeed"/>
|
||||||
|
</declare-styleable>
|
||||||
|
<color name="heart_red">#c42f0a</color>
|
||||||
|
<color name="share_blue">#0000ff</color>
|
||||||
|
<color name="black">#000000</color>
|
||||||
</resources>
|
</resources>
|
|
@ -32,18 +32,6 @@ class PostUnitTest {
|
||||||
@Test
|
@Test
|
||||||
fun getProfilePicUrlReturnsAValidURL() = Assert.assertNotNull(status.getProfilePicUrl())
|
fun getProfilePicUrlReturnsAValidURL() = Assert.assertNotNull(status.getProfilePicUrl())
|
||||||
|
|
||||||
@Test
|
|
||||||
fun getDescriptionReturnsDefaultIfEmpty() {
|
|
||||||
val emptyDescStatus = status.copy(content = "")
|
|
||||||
Assert.assertEquals( "No description", emptyDescStatus.getDescription())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun getDescriptionReturnsAValidDesc() = Assert.assertNotNull(status.getDescription())
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun getDescriptionReturnsACorrectDesc() = Assert.assertEquals(status.content, status.getDescription())
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun getUsernameReturnsACorrectName() = Assert.assertEquals(status.account.display_name, status.getUsername())
|
fun getUsernameReturnsACorrectName() = Assert.assertEquals(status.account.display_name, status.getUsername())
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.6.2'
|
classpath 'com.android.tools.build:gradle:3.6.2'
|
||||||
|
@ -20,7 +19,8 @@ allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
maven { url "https://jitpack.io" }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue