Sensitive media (#162)
* utility functions to censor and decensor a post image * added Text warning about sensitive content * adapt layout based on Sensitive attribute, censor and decensor * try to perform clicks on sensitive image * small refactor of status for sensitive layout * testing censor Matrices functions * perform test on sensitive post * modified so second post is sensitive * hide sensitiveWarning from albums for now * hide totaly the image * perform visibility check on sensitive warning textView * deleted tests using activityScenario.onActivity as they return true on assert(false) * commented dummy test for matrix censoring * implemented sensitive layout for multiple pictures posts * remove diplay check before click * now testing visibility of textView * deleted faulty lines i hope * bring back dummy check for matrices * everything is now sensitive, testing on tab post * implemented matcher for second item * implemented tests for tabs and classic sensitive layout using custom matcher Second * cleaning in JSON values, put sensitive true on posts * hide sensitive posts behind red triangle * centered background triangle * corrected indentation * extracted sensitive string in string.xml
This commit is contained in:
parent
252a192ff3
commit
5fadfd2e8e
|
@ -1,12 +1,21 @@
|
|||
package com.h.pixeldroid
|
||||
|
||||
|
||||
import android.graphics.ColorMatrix
|
||||
import android.content.Context
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.get
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.espresso.Espresso.onData
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition
|
||||
import androidx.test.espresso.contrib.RecyclerViewActions.scrollToPosition
|
||||
import androidx.test.espresso.matcher.ViewMatchers.*
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
@ -16,9 +25,18 @@ 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.second
|
||||
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.utils.PostUtils.Companion.censorColorMatrix
|
||||
import com.h.pixeldroid.utils.PostUtils.Companion.uncensorColorMatrix
|
||||
import kotlinx.android.synthetic.main.fragment_feed.*
|
||||
import kotlinx.android.synthetic.main.fragment_feed.view.*
|
||||
import kotlinx.android.synthetic.main.post_fragment.*
|
||||
import kotlinx.android.synthetic.main.post_fragment.view.*
|
||||
import org.hamcrest.Matchers.`is`
|
||||
import org.hamcrest.Matchers.not
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -283,7 +301,9 @@ class MockedServerTest {
|
|||
a -> run {
|
||||
//Wait for the feed to load
|
||||
Thread.sleep(1000)
|
||||
//Pick the second photo
|
||||
a.findViewById<TextView>(R.id.sensitiveWarning).performClick()
|
||||
Thread.sleep(1000)
|
||||
//Pick the second photo
|
||||
a.findViewById<TabLayout>(R.id.postTabs).getTabAt(1)?.select()
|
||||
}
|
||||
}
|
||||
|
@ -501,5 +521,86 @@ class MockedServerTest {
|
|||
onView(first(withId(R.id.commentContainer)))
|
||||
.check(matches(hasDescendant(withId(R.id.comment))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun censorMatrices() {
|
||||
// Doing these dummy checks as I can not get the matrix property from the ImageView
|
||||
val array: FloatArray = floatArrayOf(
|
||||
0.1f, 0f, 0f, 0f, 0f, // red vector
|
||||
0f, 0.1f, 0f, 0f, 0f, // green vector
|
||||
0f, 0f, 0.1f, 0f, 0f, // blue vector
|
||||
0f, 0f, 0f, 1f, 0f ) // alpha vector
|
||||
|
||||
assert(censorColorMatrix().equals(array))
|
||||
assert(uncensorColorMatrix().equals(ColorMatrix()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun performClickOnSensitiveWarning() {
|
||||
|
||||
onView(withId(R.id.list)).perform(scrollToPosition<PostViewHolder>(1))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(second(withId(R.id.sensitiveWarning))).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(withId(R.id.list))
|
||||
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||
(1, clickChildViewWithId(R.id.sensitiveWarning)))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(second(withId(R.id.sensitiveWarning))).check(matches(withEffectiveVisibility(Visibility.GONE)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun performClickOnPostPicture() {
|
||||
|
||||
onView(withId(R.id.list)).perform(scrollToPosition<PostViewHolder>(1))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(second(withId(R.id.sensitiveWarning))).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(withId(R.id.list))
|
||||
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||
(1, clickChildViewWithId(R.id.postPicture)))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(second(withId(R.id.sensitiveWarning))).check(matches(withEffectiveVisibility(Visibility.GONE)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun performClickOnSensitiveWarningTabs() {
|
||||
|
||||
onView(withId(R.id.list)).perform(scrollToPosition<PostViewHolder>(0))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(first(withId(R.id.sensitiveWarning))).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(withId(R.id.list))
|
||||
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||
(0, clickChildViewWithId(R.id.sensitiveWarning)))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(first(withId(R.id.sensitiveWarning))).check(matches(withEffectiveVisibility(Visibility.GONE)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun performClickOnPostPictureTabs() {
|
||||
|
||||
onView(withId(R.id.list)).perform(scrollToPosition<PostViewHolder>(0))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(first(withId(R.id.sensitiveWarning))).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(withId(R.id.list))
|
||||
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||
(0, clickChildViewWithId(R.id.postPicture)))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(first(withId(R.id.sensitiveWarning))).check(matches(withEffectiveVisibility(Visibility.GONE)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,25 @@ abstract class CustomMatchers {
|
|||
}
|
||||
}
|
||||
|
||||
fun <T> second(matcher: Matcher<T>): Matcher<T>? {
|
||||
return object : BaseMatcher<T>() {
|
||||
var isFirst = true
|
||||
override fun describeTo(description: org.hamcrest.Description?) {
|
||||
description?.appendText("second matching item")
|
||||
}
|
||||
|
||||
override fun matches(item: Any?): Boolean {
|
||||
if (isFirst && matcher.matches(item)) {
|
||||
isFirst = false
|
||||
return false
|
||||
} else if (!isFirst && matcher.matches(item))
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param percent can be 1 or 0
|
||||
* 1: swipes all the way up
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -14,7 +14,6 @@ class MockServer {
|
|||
private val headerValue = "application/json; charset=utf-8"
|
||||
}
|
||||
|
||||
|
||||
fun start() {
|
||||
try {
|
||||
server.dispatcher = getDispatcher()
|
||||
|
|
|
@ -32,7 +32,12 @@ class ProfilePostsRecyclerViewAdapter: RecyclerView.Adapter<ProfilePostsRecycler
|
|||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val post = posts[position]
|
||||
setSquareImageFromURL(holder.postView, post.getPostPreviewURL(), holder.postPreview)
|
||||
|
||||
if (post.sensitive)
|
||||
setSquareImageFromURL(holder.postView, null, holder.postPreview)
|
||||
else
|
||||
setSquareImageFromURL(holder.postView, post.getPostPreviewURL(), holder.postPreview)
|
||||
|
||||
holder.postPreview.setOnClickListener {
|
||||
val intent = Intent(holder.postPreview.context, PostActivity::class.java)
|
||||
intent.putExtra(Status.POST_TAG, post)
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.h.pixeldroid.objects
|
|||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.graphics.ColorMatrixColorFilter
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.Spanned
|
||||
|
@ -30,17 +31,20 @@ import com.h.pixeldroid.utils.HtmlUtils.Companion.getDomain
|
|||
import com.h.pixeldroid.utils.HtmlUtils.Companion.parseHTMLText
|
||||
import com.h.pixeldroid.utils.ImageConverter
|
||||
import com.h.pixeldroid.utils.ImageUtils.Companion.downloadImage
|
||||
import com.h.pixeldroid.utils.PostUtils.Companion.censorColorMatrix
|
||||
import com.h.pixeldroid.utils.PostUtils.Companion.likePostCall
|
||||
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.toggleCommentInput
|
||||
import com.h.pixeldroid.utils.PostUtils.Companion.unLikePostCall
|
||||
import com.h.pixeldroid.utils.PostUtils.Companion.uncensorColorMatrix
|
||||
import com.h.pixeldroid.utils.PostUtils.Companion.undoReblogPost
|
||||
import com.karumi.dexter.Dexter
|
||||
import com.karumi.dexter.listener.PermissionDeniedResponse
|
||||
import com.karumi.dexter.listener.PermissionGrantedResponse
|
||||
import com.karumi.dexter.listener.single.BasePermissionListener
|
||||
import kotlinx.android.synthetic.main.post_fragment.view.*
|
||||
import kotlinx.android.synthetic.main.post_fragment.view.postDate
|
||||
import kotlinx.android.synthetic.main.post_fragment.view.postDomain
|
||||
import java.io.Serializable
|
||||
|
@ -48,10 +52,6 @@ import java.text.ParseException
|
|||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlinx.android.synthetic.main.post_fragment.view.postPager
|
||||
import kotlinx.android.synthetic.main.post_fragment.view.postPicture
|
||||
import kotlinx.android.synthetic.main.post_fragment.view.postTabs
|
||||
import kotlinx.android.synthetic.main.post_fragment.view.profilePic
|
||||
|
||||
/*
|
||||
Represents a status posted by an account.
|
||||
|
@ -92,7 +92,7 @@ data class Status(
|
|||
val muted: Boolean = false,
|
||||
val bookmarked: Boolean = false,
|
||||
val pinned: Boolean = false
|
||||
) : Serializable, FeedContent()
|
||||
) : Serializable, FeedContent()
|
||||
{
|
||||
|
||||
companion object {
|
||||
|
@ -149,7 +149,7 @@ data class Status(
|
|||
android.text.format.DateUtils.SECOND_IN_MILLIS,
|
||||
android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE)
|
||||
textView.text = if(isActivity) "Posted on $date"
|
||||
else "$formattedDate"
|
||||
else "$formattedDate"
|
||||
} catch (e: ParseException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
@ -163,28 +163,47 @@ data class Status(
|
|||
}
|
||||
|
||||
private fun setupPostPics(rootView: View, request: RequestBuilder<Drawable>, homeFragment: Fragment) {
|
||||
//Check whether or not we need to activate the viewPager
|
||||
if(media_attachments?.size == 1) {
|
||||
rootView.postPicture.visibility = VISIBLE
|
||||
rootView.postPager.visibility = GONE
|
||||
rootView.postTabs.visibility = GONE
|
||||
|
||||
// Standard layout
|
||||
rootView.postPicture.visibility = VISIBLE
|
||||
rootView.postPager.visibility = GONE
|
||||
rootView.postTabs.visibility = GONE
|
||||
|
||||
if (sensitive) {
|
||||
setupSensitiveLayout(rootView, request, homeFragment)
|
||||
request.load(this.getPostUrl()).into(rootView.postPicture)
|
||||
} else if(media_attachments?.size!! > 1) {
|
||||
//Only show the viewPager and tabs
|
||||
rootView.postPicture.visibility = GONE
|
||||
rootView.postPager.visibility = VISIBLE
|
||||
rootView.postTabs.visibility = VISIBLE
|
||||
|
||||
val tabs : ArrayList<ImageFragment> = ArrayList()
|
||||
} else {
|
||||
rootView.sensitiveWarning.visibility = GONE
|
||||
|
||||
//Fill the tabs with each mediaAttachment
|
||||
for(media in media_attachments) {
|
||||
tabs.add(ImageFragment.newInstance(media.url))
|
||||
if(media_attachments?.size == 1) {
|
||||
request.load(this.getPostUrl()).into(rootView.postPicture)
|
||||
|
||||
} else if(media_attachments?.size!! > 1) {
|
||||
setupTabsLayout(rootView, request, homeFragment)
|
||||
}
|
||||
setupTabs(tabs, rootView, homeFragment)
|
||||
|
||||
imagePopUpMenu(rootView, homeFragment.requireActivity())
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupTabsLayout(rootView: View, request: RequestBuilder<Drawable>, homeFragment: Fragment) {
|
||||
//Only show the viewPager and tabs
|
||||
rootView.postPicture.visibility = GONE
|
||||
rootView.postPager.visibility = VISIBLE
|
||||
rootView.postTabs.visibility = VISIBLE
|
||||
|
||||
val tabs : ArrayList<ImageFragment> = ArrayList()
|
||||
|
||||
//Fill the tabs with each mediaAttachment
|
||||
for(media in media_attachments!!) {
|
||||
tabs.add(ImageFragment.newInstance(media.url))
|
||||
}
|
||||
|
||||
setupTabs(tabs, rootView, homeFragment)
|
||||
}
|
||||
|
||||
|
||||
private fun setupTabs(tabs: ArrayList<ImageFragment>, rootView: View, homeFragment: Fragment) {
|
||||
//Attach the given tabs to the view pager
|
||||
rootView.postPager.adapter = object : FragmentStateAdapter(homeFragment) {
|
||||
|
@ -196,6 +215,7 @@ data class Status(
|
|||
return media_attachments?.size ?: 0
|
||||
}
|
||||
}
|
||||
|
||||
TabLayoutMediator(rootView.postTabs, rootView.postPager) { tab, _ ->
|
||||
tab.icon = rootView.context.getDrawable(R.drawable.ic_dot_blue_12dp)
|
||||
}.attach()
|
||||
|
@ -250,9 +270,7 @@ data class Status(
|
|||
|
||||
|
||||
//Set comment initial visibility
|
||||
rootView.findViewById<LinearLayout>(R.id.commentIn).visibility = GONE
|
||||
|
||||
imagePopUpMenu(rootView, homeFragment.requireActivity())
|
||||
rootView.findViewById<LinearLayout>(R.id.commentIn).visibility = View.GONE
|
||||
}
|
||||
|
||||
fun setDescription(rootView: View, api : PixelfedAPI, credential: String) {
|
||||
|
@ -411,4 +429,31 @@ data class Status(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupSensitiveLayout(view: View, request: RequestBuilder<Drawable>, homeFragment: Fragment) {
|
||||
|
||||
// Set dark layout and warning message
|
||||
view.sensitiveWarning.visibility = VISIBLE
|
||||
view.postPicture.colorFilter = ColorMatrixColorFilter(censorColorMatrix())
|
||||
|
||||
fun uncensorPicture(view: View) {
|
||||
if (!media_attachments.isNullOrEmpty()) {
|
||||
view.sensitiveWarning.visibility = GONE
|
||||
view.postPicture.colorFilter = ColorMatrixColorFilter(uncensorColorMatrix())
|
||||
|
||||
if (media_attachments.size > 1)
|
||||
setupTabsLayout(view, request, homeFragment)
|
||||
}
|
||||
imagePopUpMenu(view, homeFragment.requireActivity())
|
||||
}
|
||||
|
||||
|
||||
view.findViewById<TextView>(R.id.sensitiveWarning).setOnClickListener {
|
||||
uncensorPicture(view)
|
||||
}
|
||||
|
||||
view.findViewById<ImageView>(R.id.postPicture).setOnClickListener {
|
||||
uncensorPicture(view)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,11 @@
|
|||
package com.h.pixeldroid.utils
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.ColorMatrix
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.api.PixelfedAPI
|
||||
import com.h.pixeldroid.fragments.feeds.PostViewHolder
|
||||
|
@ -226,5 +225,14 @@ abstract class PostUtils {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun censorColorMatrix(): ColorMatrix {
|
||||
val array: FloatArray = floatArrayOf( 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 1f, 0f )
|
||||
return ColorMatrix(array)
|
||||
}
|
||||
|
||||
fun uncensorColorMatrix(): ColorMatrix {
|
||||
return ColorMatrix()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,6 +14,10 @@
|
|||
android:id="@+id/postPreview"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:layout_gravity="center"
|
||||
android:adjustViewBounds="false"
|
||||
android:background="@android:drawable/stat_sys_warning"
|
||||
android:backgroundTint="#780000"
|
||||
android:contentDescription="TODO" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
|
|
@ -68,8 +68,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
</androidx.viewpager2.widget.ViewPager2>
|
||||
app:layout_constraintTop_toTopOf="parent"></androidx.viewpager2.widget.ViewPager2>
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/postTabs"
|
||||
|
@ -78,8 +77,7 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/postPager"
|
||||
app:tabMode="auto">
|
||||
</com.google.android.material.tabs.TabLayout>
|
||||
app:tabMode="auto"></com.google.android.material.tabs.TabLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/postPicture"
|
||||
|
@ -104,8 +102,23 @@
|
|||
app:layout_constraintTop_toTopOf="@+id/postPicture"
|
||||
app:layout_constraintVertical_bias="0.1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sensitiveWarning"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:adjustViewBounds="true"
|
||||
android:gravity="center|center_horizontal|center_vertical"
|
||||
android:longClickable="true"
|
||||
android:text="@string/cw_nsfw_hidden_media_n_click_to_show"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
android:textColor="@color/ic_launcher_background"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/postPicture"
|
||||
app:layout_constraintTop_toBottomOf="@+id/postTabs"
|
||||
tools:src="@color/browser_actions_bg_grey" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/commenter"
|
||||
android:layout_width="30dp"
|
||||
|
|
|
@ -55,5 +55,6 @@
|
|||
<string name="enter">Enter</string>
|
||||
<string name="auth_error_toast_msg">Server has responded with an error, try again!</string>
|
||||
<string name="login_empty_string_error">Instance address cannot be empty!</string>
|
||||
<string name="cw_nsfw_hidden_media_n_click_to_show">CW / NSFW / Hidden Media \n (click to show)</string>
|
||||
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue