Add reporting and link sharing functionality, polish UI

This commit is contained in:
Matthieu 2020-10-04 23:27:06 +02:00
parent a769d404a6
commit 50dd0bad51
65 changed files with 1021 additions and 606 deletions

View File

@ -65,7 +65,7 @@ dependencies {
* AndroidX dependencies:
*/
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.core:core-ktx:1.3.1'
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
implementation 'androidx.navigation:navigation-fragment:2.3.0'
@ -80,6 +80,7 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0'
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
implementation "androidx.annotation:annotation:1.1.0"
implementation 'androidx.gridlayout:gridlayout:1.0.0'
// Use the most recent version of CameraX
def camerax_version = '1.0.0-beta08'
@ -104,8 +105,6 @@ dependencies {
implementation 'com.google.android.material:material:1.2.1'
implementation 'com.google.android:flexbox:2.0.1'
//Dagger (dependency injection)
implementation 'com.google.dagger:dagger-android:2.28.3'
implementation 'com.google.dagger:dagger-android-support:2.28.3'
@ -138,13 +137,14 @@ dependencies {
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation "com.mikepenz:materialdrawer:8.1.4"
implementation "com.mikepenz:materialdrawer:8.1.5"
// Add for NavController support
implementation "com.mikepenz:materialdrawer-nav:8.0.3"
implementation "com.mikepenz:materialdrawer-nav:8.1.5"
//iconics
implementation "com.mikepenz:materialdrawer-iconics:8.1.4"
implementation "com.mikepenz:iconics-views:5.0.2"
implementation "com.mikepenz:iconics-core:5.0.3"
implementation "com.mikepenz:materialdrawer-iconics:8.1.5"
implementation "com.mikepenz:iconics-views:5.0.3"
implementation 'com.mikepenz:google-material-typeface:3.0.1.4.original-kotlin@aar'

View File

@ -24,9 +24,12 @@
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:replace="android:allowBackup">
<activity android:name=".CameraActivity"></activity>
<activity
android:name=".PhotoEditActivity"
android:theme="@style/AppTheme.NoActionBar" />
android:name=".ReportActivity"
android:screenOrientation="sensorPortrait"
tools:ignore="LockedOrientationActivity" />
<activity android:name=".PhotoEditActivity" />
<activity
android:name=".PostCreationActivity"
android:screenOrientation="sensorPortrait"
@ -47,19 +50,24 @@
<activity
android:name=".SettingsActivity"
android:label="@string/title_activity_settings2"
android:parentActivityName=".MainActivity"
android:screenOrientation="sensorPortrait"
tools:ignore="LockedOrientationActivity"
android:parentActivityName=".MainActivity" />
tools:ignore="LockedOrientationActivity" />
<activity
android:name=".MainActivity"
android:screenOrientation="sensorPortrait"
android:theme="@style/AppTheme.Launcher"
android:windowSoftInputMode="adjustPan"
tools:ignore="LockedOrientationActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.default_searchable"
android:value=".SearchActivity" />
</activity>
<activity
android:name=".LoginActivity"
@ -89,28 +97,23 @@
android:screenOrientation="sensorPortrait"
tools:ignore="LockedOrientationActivity">
<intent-filter>
<action
android:name="android.intent.action.SEARCH"
android:screenOrientation="sensorPortrait"
tools:ignore="LockedOrientationActivity" />
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>
<activity
android:name=".AboutActivity"
android:parentActivityName=".SettingsActivity"
android:screenOrientation="sensorPortrait"
tools:ignore="LockedOrientationActivity"
android:parentActivityName=".SettingsActivity" />
<activity android:name=".LicenseActivity"
tools:ignore="LockedOrientationActivity" />
<activity
android:name=".LicenseActivity"
android:parentActivityName=".AboutActivity"
android:screenOrientation="sensorPortrait"
tools:ignore="LockedOrientationActivity"
android:parentActivityName=".AboutActivity" />
tools:ignore="LockedOrientationActivity" />
<provider
android:name="androidx.core.content.FileProvider"

View File

@ -0,0 +1,28 @@
package com.h.pixeldroid
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.h.pixeldroid.fragments.CameraFragment
class CameraActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_camera)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setTitle(R.string.add_photo)
val cameraFragment = CameraFragment()
val arguments = Bundle()
arguments.putBoolean("CameraActivity", true)
cameraFragment.arguments = arguments
supportFragmentManager.beginTransaction()
.add(R.id.camera_activity_fragment, cameraFragment).commit()
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
}

View File

@ -88,7 +88,7 @@ class MainActivity : AppCompatActivity() {
}
private fun setupDrawer() {
main_toolbar.setNavigationOnClickListener {
main_drawer_button.setOnClickListener{
drawer_layout.open()
}

View File

@ -103,11 +103,9 @@ class PhotoEditActivity : AppCompatActivity(), FilterListFragmentListener, EditI
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_photo_edit)
//TODO move to xml:
setSupportActionBar(toolbar)
supportActionBar!!.title = "Edit"
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
supportActionBar!!.setHomeButtonEnabled(true)
supportActionBar?.setTitle(R.string.toolbar_title_edit)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setHomeButtonEnabled(true)
val cropButton: FloatingActionButton = findViewById(R.id.cropImageButton)
@ -267,7 +265,11 @@ class PhotoEditActivity : AppCompatActivity(), FilterListFragmentListener, EditI
private fun startCrop() {
val file = File.createTempFile("temp_crop_img", ".png", cacheDir)
val uCrop: UCrop = UCrop.of(initialUri!!, Uri.fromFile(file))
val options: UCrop.Options = UCrop.Options().apply {
setStatusBarColor(resources.getColor(R.color.colorPrimaryDark, theme))
setActiveControlsWidgetColor(resources.getColor(R.color.colorButtonBg, theme))
}
val uCrop: UCrop = UCrop.of(initialUri!!, Uri.fromFile(file)).withOptions(options)
uCrop.start(this)
}
@ -470,6 +472,7 @@ class PhotoEditActivity : AppCompatActivity(), FilterListFragmentListener, EditI
if(!save) {
sendBackImage(path)
} else {
if(path.startsWith("file")) {
MediaScannerConnection.scanFile(
this,
arrayOf(path.toUri().toFile().absolutePath),
@ -477,7 +480,11 @@ class PhotoEditActivity : AppCompatActivity(), FilterListFragmentListener, EditI
) { path, uri ->
if (uri == null) {
Log.e("NEW IMAGE SCAN FAILED", "Tried to scan $path, but it failed")
Log.e(
"NEW IMAGE SCAN FAILED",
"Tried to scan $path, but it failed"
)
}
}
}

View File

@ -4,6 +4,7 @@ import android.app.Application
import androidx.preference.PreferenceManager
import com.h.pixeldroid.di.*
import com.h.pixeldroid.utils.ThemeUtils
import com.mikepenz.iconics.Iconics
import org.ligi.tracedroid.TraceDroid
@ -23,7 +24,9 @@ class Pixeldroid: Application() {
.databaseModule(DatabaseModule(applicationContext))
.aPIModule(APIModule())
.build()
mApplicationComponent.inject(this);
mApplicationComponent.inject(this)
Iconics.init(applicationContext)
}
fun getAppComponent(): ApplicationComponent {

View File

@ -1,7 +1,6 @@
package com.h.pixeldroid
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
@ -26,7 +25,6 @@ import com.h.pixeldroid.interfaces.PostCreationListener
import com.h.pixeldroid.objects.Attachment
import com.h.pixeldroid.objects.Instance
import com.h.pixeldroid.objects.Status
import com.h.pixeldroid.utils.DBUtils
import com.h.pixeldroid.utils.ProgressRequestBody
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
@ -39,9 +37,11 @@ import retrofit2.Callback
import retrofit2.Response
import javax.inject.Inject
class PostCreationActivity : AppCompatActivity(), PostCreationListener {
private val TAG = "Post Creation Activity"
private val MORE_PICTURES_REQUEST_CODE = 0xffff
class PostCreationActivity : AppCompatActivity(), PostCreationListener {
private lateinit var recycler : RecyclerView
private lateinit var adapter : PostCreationAdapter
@ -74,11 +74,14 @@ class PostCreationActivity : AppCompatActivity(), PostCreationListener {
(this.application as Pixeldroid).getAppComponent().inject(this)
// load images
posts = intent.getStringArrayListExtra("pictures_uri")!!
progressList = posts.map { 0 } as ArrayList<Int>
muListOfIds = posts.map { "" }.toMutableList()
// get image URIs
if(intent.clipData != null) {
val count = intent.clipData!!.itemCount
for (i in 0 until count) {
val imageUri: String = intent.clipData!!.getItemAt(i).uri.toString()
posts.add(imageUri)
}
}
user = db.userDao().getActiveUser()
@ -100,12 +103,14 @@ class PostCreationActivity : AppCompatActivity(), PostCreationListener {
// TODO
//upload the picture and display progress while doing so
muListOfIds = posts.map { "" }.toMutableList()
progressList = posts.map { 0 } as ArrayList<Int>
upload()
adapter = PostCreationAdapter(posts)
adapter.listener = this
recycler = findViewById(R.id.image_grid)
recycler.layoutManager = GridLayoutManager(this, if (posts.size > 2) 2 else 1)
recycler.layoutManager = GridLayoutManager(this, 3)
recycler.adapter = adapter
// get the description and send the post
@ -126,8 +131,8 @@ class PostCreationActivity : AppCompatActivity(), PostCreationListener {
val textField = findViewById<TextInputEditText>(R.id.new_post_description_input_field)
val content = textField.text.toString()
if (content.length > maxLength) {
// error, too much characters
textField.error = "Description must contain $maxLength characters at most."
// error, too many characters
textField.error = getString(R.string.description_max_characters).format(maxLength)
return false
}
// store the description
@ -135,9 +140,26 @@ class PostCreationActivity : AppCompatActivity(), PostCreationListener {
return true
}
private fun upload() {
for ((index, post) in posts.withIndex()) {
val imageUri = Uri.parse(post)
/**
* Uploads the images that are in the [posts] array.
* Keeps track of them in the [progressList] (for the upload progress), and the [muListOfIds]
* (for the list of ids of the uploads).
* @param newImagesStartingIndex is the index in the [posts] array we want to start uploading at.
* Indices before this are already uploading, or done uploading, from before.
* @param editedImage contains the index of the image that was edited. If set, other images are
* not uploaded again: they should already be uploading, or be done uploading, from before.
*/
private fun upload(newImagesStartingIndex: Int = 0, editedImage: Int? = null) {
enableButton(false)
uploadProgressBar.visibility = View.VISIBLE
upload_completed_textview.visibility = View.INVISIBLE
val range: IntRange = if(editedImage == null){
newImagesStartingIndex until posts.size
} else IntRange(editedImage, editedImage)
for (index in range) {
val imageUri = Uri.parse(posts[index])
val imageInputStream = contentResolver.openInputStream(imageUri)!!
val size =
@ -259,50 +281,84 @@ class PostCreationActivity : AppCompatActivity(), PostCreationListener {
if (resultCode == Activity.RESULT_OK && data != null) {
posts[positionResult] = data.getStringExtra("result")!!
adapter.notifyItemChanged(positionResult)
muListOfIds.clear()
upload()
}
else if(resultCode == Activity.RESULT_CANCELED){
Toast.makeText(applicationContext, "Edition cancelled", Toast.LENGTH_SHORT).show()
muListOfIds[positionResult] = ""
progressList[positionResult] = 0
upload(editedImage = positionResult)
} else if(resultCode == Activity.RESULT_CANCELED){
Toast.makeText(applicationContext, "Editing cancelled", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(applicationContext, "Error while editing", Toast.LENGTH_SHORT).show()
}
} else if (requestCode == MORE_PICTURES_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK && data?.clipData != null) {
val count = data.clipData!!.itemCount
for (i in 0 until count) {
val imageUri: String = data.clipData!!.getItemAt(i).uri.toString()
posts.add(imageUri)
progressList.add(0)
muListOfIds.add(i, "")
}
adapter.notifyDataSetChanged()
upload(newImagesStartingIndex = posts.size - count)
} else if(resultCode == Activity.RESULT_CANCELED){
Toast.makeText(applicationContext, "Adding images", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(applicationContext, "Error while adding images", Toast.LENGTH_SHORT).show()
}
}
}
class PostCreationAdapter(private val posts: ArrayList<String>): RecyclerView.Adapter<PostCreationAdapter.ViewHolder>() {
private var context: Context? = null
inner class PostCreationAdapter(private val posts: ArrayList<String>): RecyclerView.Adapter<PostCreationAdapter.ViewHolder>() {
var listener: PostCreationListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
context = parent.context
val view = LayoutInflater.from(parent.context)
val view =
if(viewType == 0) LayoutInflater.from(parent.context)
.inflate(R.layout.image_album_creation, parent, false)
else LayoutInflater.from(parent.context)
.inflate(R.layout.add_more_album_creation, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
Log.d("test", "binded")
holder.bind()
override fun getItemViewType(position: Int): Int {
if(position == posts.size) return 1
return 0
}
override fun getItemCount(): Int = posts.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if(position != posts.size) {
holder.bindImage()
} else{
holder.bindPlusButton()
}
}
override fun getItemCount(): Int = posts.size + 1
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind() {
val image = Uri.parse(posts[adapterPosition])
fun bindImage() {
val image = Uri.parse(
posts[adapterPosition]
)
// load image
Glide.with(context!!)
Glide.with(itemView.context)
.load(image)
.centerCrop()
.into(itemView.galleryImage)
// adding click or tap handler for the image layout
itemView.galleryImage.setOnClickListener {
Log.d("test", "clicked")
itemView.setOnClickListener {
listener?.onClick(adapterPosition)
}
}
fun bindPlusButton() {
itemView.setOnClickListener {
val intent = Intent(itemView.context, CameraActivity::class.java)
this@PostCreationActivity.startActivityForResult(intent, MORE_PICTURES_REQUEST_CODE)
}
}
}
}

View File

@ -0,0 +1,81 @@
package com.h.pixeldroid
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import com.h.pixeldroid.db.AppDatabase
import com.h.pixeldroid.di.PixelfedAPIHolder
import com.h.pixeldroid.objects.Report
import com.h.pixeldroid.objects.Status
import kotlinx.android.synthetic.main.activity_report.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import javax.inject.Inject
class ReportActivity : AppCompatActivity() {
@Inject
lateinit var db: AppDatabase
@Inject
lateinit var apiHolder: PixelfedAPIHolder
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_report)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setTitle(R.string.report)
val status = intent.getSerializableExtra(Status.POST_TAG) as Status?
(this.application as Pixeldroid).getAppComponent().inject(this)
//get the currently active user
val user = db.userDao().getActiveUser()
report_target_textview.text = getString(R.string.report_target).format(status?.account?.acct)
reportButton.setOnClickListener{
reportButton.visibility = View.INVISIBLE
reportProgressBar.visibility = View.VISIBLE
textInputLayout.editText?.isEnabled = false
val accessToken = user?.accessToken.orEmpty()
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
api.report("Bearer $accessToken", status?.account?.id!!, listOf(status), textInputLayout.editText?.text.toString())
.enqueue(object : Callback<Report> {
override fun onResponse(
call: Call<Report>,
response: Response<Report>
) {
if (response.body() == null || !response.isSuccessful) {
textInputLayout.error = getString(R.string.report_error)
reportButton.visibility = View.VISIBLE
textInputLayout.editText?.isEnabled = true
reportProgressBar.visibility = View.GONE
} else {
reportProgressBar.visibility = View.GONE
reportButton.isEnabled = false
reportButton.text = getString(R.string.reported)
reportButton.visibility = View.VISIBLE
}
}
override fun onFailure(call: Call<Report>, t: Throwable) {
Log.e("REPORT:", t.toString())
}
})
}
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
}

View File

@ -20,9 +20,15 @@ class SearchActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_search)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
var query = ""
if (Intent.ACTION_SEARCH == intent.action) {
query = intent.getStringExtra(SearchManager.QUERY).orEmpty()
}
var query = intent.getSerializableExtra("searchFeed") as String
query = query.trim()
supportActionBar?.title = query
val searchType = when {
query.startsWith("#") -> {
@ -41,6 +47,11 @@ class SearchActivity : AppCompatActivity() {
setupTabs(tabs, searchType)
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
private fun createSearchTabs(query: String): Array<Fragment>{
val searchFeedFragment =

View File

@ -54,13 +54,6 @@ class SettingsActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferen
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
val button: Preference? = findPreference("about")
button?.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
val intent = Intent(context, AboutActivity::class.java)
startActivity(intent)
true
}
}
}

View File

@ -266,4 +266,15 @@ interface PixelfedAPI {
fun discover(
@Header("Authorization") authorization: String
) : Call<DiscoverPosts>
@FormUrlEncoded
@POST("/api/v1/reports")
fun report(
@Header("Authorization") authorization: String,
@Field("account_id") account_id: String,
@Field("status_ids") status_ids: List<Status>,
@Field("comment") comment: String,
@Field("forward") forward: Boolean = true
) : Call<Report>
}

View File

@ -24,6 +24,7 @@ interface ApplicationComponent {
fun inject(activity: PostCreationActivity?)
fun inject(activity: ProfileActivity?)
fun inject(mainActivity: MainActivity?)
fun inject(activity: ReportActivity?)
fun inject(fragment: PostFragment)
fun inject(fragment: SearchDiscoverFragment)
fun inject(fragment: OfflineFeedFragment)

View File

@ -2,6 +2,7 @@ package com.h.pixeldroid.fragments
import android.Manifest
import android.app.Activity
import android.content.ClipData
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
@ -23,13 +24,14 @@ import androidx.camera.view.PreviewView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.core.view.setPadding
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.h.pixeldroid.PhotoEditActivity
import com.h.pixeldroid.PostCreationActivity
import com.h.pixeldroid.CameraActivity
import com.h.pixeldroid.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -39,6 +41,7 @@ import java.util.concurrent.Executors
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.properties.Delegates
// This is an arbitrary number we are using to keep track of the permission
// request. Where an app has multiple context for requesting permission,
@ -54,7 +57,10 @@ class CameraFragment : Fragment() {
private lateinit var container: ConstraintLayout
private lateinit var viewFinder: PreviewView
private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE)
private val REQUIRED_PERMISSIONS = arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE
)
private val PICK_IMAGE_REQUEST = 1
private val CAPTURE_IMAGE_REQUEST = 2
@ -64,6 +70,8 @@ class CameraFragment : Fragment() {
private var imageCapture: ImageCapture? = null
private var camera: Camera? = null
private var inActivity by Delegates.notNull<Boolean>()
/** Blocking camera operations are performed using this executor */
private lateinit var cameraExecutor: ExecutorService
@ -90,7 +98,8 @@ class CameraFragment : Fragment() {
*/
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(
requireContext(), it) == PackageManager.PERMISSION_GRANTED
requireContext(), it
) == PackageManager.PERMISSION_GRANTED
}
override fun onDestroyView() {
@ -105,8 +114,12 @@ class CameraFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.fragment_camera, container, false)
savedInstanceState: Bundle?
): View? {
inActivity = arguments?.getBoolean("CameraActivity") ?: false
return inflater.inflate(R.layout.fragment_camera, container, false)
}
private fun setGalleryThumbnail(uri: String) {
// Reference of the view that holds the gallery thumbnail
@ -203,7 +216,8 @@ class CameraFragment : Fragment() {
// A variable number of use-cases can be passed here -
// camera provides access to CameraControl & CameraInfo
camera = cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture)
this, cameraSelector, preview, imageCapture
)
// Attach the viewfinder's surface provider to preview use case
preview?.setSurfaceProvider(viewFinder.createSurfaceProvider())
@ -246,7 +260,6 @@ class CameraFragment : Fragment() {
// In the background, load latest photo taken (if any) for gallery thumbnail
lifecycleScope.launch(Dispatchers.IO) {
// Find the last picture
// Find the last picture
val projection = arrayOf(
MediaStore.Images.ImageColumns._ID,
@ -382,10 +395,29 @@ class CameraFragment : Fragment() {
}
private fun startAlbumCreation(uris: ArrayList<String>) {
startActivity(
Intent(activity, PostCreationActivity::class.java)
.putExtra("pictures_uri", uris)
)
val intent = Intent(requireActivity(), PostCreationActivity::class.java)
.apply {
uris.forEach{
//Why are we using ClipData here? Because the FLAG_GRANT_READ_URI_PERMISSION
//needs to be applied to the URIs, and this flag flag only applies to the
//Intent's data and any URIs specified in its ClipData.
if(clipData == null){
clipData = ClipData("", emptyArray(), ClipData.Item(it.toUri()))
} else {
clipData!!.addItem(ClipData.Item(it.toUri()))
}
}
addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
if(inActivity){
requireActivity().setResult(Activity.RESULT_OK, intent)
requireActivity().finish()
} else {
startActivity(intent)
}
}
companion object {

View File

@ -50,11 +50,14 @@ class FilterListFragment : Fragment(), FilterListFragmentListener {
recyclerView.addItemDecoration(SpaceItemDecoration(space))
recyclerView.adapter = adapter
displayImage(null)
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
displayImage(null)
}
private fun displayImage(bitmap: Bitmap?) {
val r = Runnable {
val tbImage: Bitmap = (if (bitmap == null) {
@ -74,10 +77,10 @@ class FilterListFragment : Fragment(), FilterListFragmentListener {
})
?: return@Runnable
setupFilter(tbImage)
if(activity != null) setupFilter(tbImage)
tbItemList.addAll(ThumbnailsManager.processThumbs(activity))
requireActivity().runOnUiThread{ adapter.notifyDataSetChanged() }
if(context != null) tbItemList.addAll(ThumbnailsManager.processThumbs(context))
activity?.runOnUiThread{ adapter.notifyDataSetChanged() }
}
Thread(r).start()
@ -93,7 +96,7 @@ class FilterListFragment : Fragment(), FilterListFragmentListener {
tbItem.filterName = tbItem.filter.name
ThumbnailsManager.addThumb(tbItem)
val filters = FilterPack.getFilterPack(requireActivity())
val filters = FilterPack.getFilterPack(context)
for (filter in filters) {
val item = ThumbnailItem()

View File

@ -1,6 +1,5 @@
package com.h.pixeldroid.fragments
import android.Manifest
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
@ -9,18 +8,14 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.PopupMenu
import android.widget.Toast
import com.bumptech.glide.Glide
import com.google.android.material.snackbar.Snackbar
import com.h.pixeldroid.R
import com.h.pixeldroid.utils.ImageUtils
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.fragment_image.*
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val IMG_URL = "imgurl"
private const val IMG_DESCRIPTION = "imgdescription"
private const val RQST_BLDR = "rqstbldr"
/**
@ -30,11 +25,13 @@ private const val RQST_BLDR = "rqstbldr"
*/
class ImageFragment : Fragment() {
private lateinit var imgUrl: String
private lateinit var imgDescription: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
imgUrl = it.getString(IMG_URL)!!
imgDescription = it.getString(IMG_DESCRIPTION)!!.ifEmpty { getString(R.string.no_description) }
}
}
@ -45,49 +42,10 @@ class ImageFragment : Fragment() {
val view = inflater.inflate(R.layout.fragment_image, container, false)
view.findViewById<ImageView>(R.id.imageImageView).setOnLongClickListener {
PopupMenu(view.context, it).apply {
setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.image_popup_menu_save_to_gallery -> {
Dexter.withContext(view.context)
.withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.withListener(object: BasePermissionListener() {
override fun onPermissionDenied(p0: PermissionDeniedResponse?) {
Toast.makeText(view.context,
view.context.getString(R.string.write_permission_download_pic),
Toast.LENGTH_SHORT).show()
Snackbar.make(it, imgDescription, Snackbar.LENGTH_SHORT).show()
true
}
override fun onPermissionGranted(p0: PermissionGrantedResponse?) {
ImageUtils.downloadImage(requireActivity(), imgUrl)
}
}).check()
true
}
R.id.image_popup_menu_share_picture -> {
Dexter.withContext(view.context)
.withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.withListener(object: BasePermissionListener() {
override fun onPermissionDenied(p0: PermissionDeniedResponse?) {
Toast.makeText(view.context,
view.context.getString(R.string.write_permission_share_pic),
Toast.LENGTH_SHORT).show()
}
override fun onPermissionGranted(p0: PermissionGrantedResponse?) {
ImageUtils.downloadImage(requireActivity(), imgUrl, share = true)
}
}).check()
true
}
else -> false
}
}
inflate(R.menu.image_popup_menu)
show()
}
true
}
// Inflate the layout for this fragment
return view
}
@ -100,6 +58,7 @@ class ImageFragment : Fragment() {
.placeholder(ColorDrawable(Color.GRAY))
.load(imgUrl)
.into(view.findViewById(R.id.imageImageView)!!)
imageImageView.contentDescription = imgDescription
}
companion object {
@ -111,10 +70,11 @@ class ImageFragment : Fragment() {
* @return A new instance of fragment ImageFragment.
*/
@JvmStatic
fun newInstance(imageUrl: String) =
fun newInstance(imageUrl: String, imageDescription: String) =
ImageFragment().apply {
arguments = Bundle().apply {
putString(IMG_URL, imageUrl)
putString(IMG_DESCRIPTION, imageDescription)
}
}
}

View File

@ -39,14 +39,6 @@ class PostFragment : Fragment() {
.asDrawable().fitCenter()
.placeholder(ColorDrawable(Color.GRAY))
currentStatus?.setupPost(root, picRequest, this, statusDomain, true)
//Setup arguments needed for the onclicklisteners
val holder = PostViewHolder(
root,
requireContext()
)
(requireActivity().application as Pixeldroid).getAppComponent().inject(this)
val user = db.userDao().getActiveUser()
@ -54,21 +46,17 @@ class PostFragment : Fragment() {
val accessToken = user?.accessToken.orEmpty()
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
currentStatus?.setDescription(root, api, "Bearer $accessToken")
currentStatus?.setupPost(root, picRequest, this, statusDomain, true)
//Activate onclickListeners
currentStatus?.activateLiker(holder, api, "Bearer $accessToken",
currentStatus.favourited ?: false
val holder = PostViewHolder(
root,
root.context
)
currentStatus?.activateReblogger(holder, api, "Bearer $accessToken",
currentStatus.reblogged ?: false
)
currentStatus?.activateCommenter(holder, api, "Bearer $accessToken")
currentStatus?.showComments(holder, api, "Bearer $accessToken")
//Activate double tap liking
currentStatus?.activateDoubleTapLiker(holder, api, "Bearer $accessToken")
currentStatus?.activateButtons(holder, api, "Bearer $accessToken")
return root
}
}

View File

@ -1,19 +1,21 @@
package com.h.pixeldroid.fragments
import android.app.SearchManager
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.*
import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat.getSystemService
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.google.android.material.textview.MaterialTextView
import com.h.pixeldroid.Pixeldroid
import com.h.pixeldroid.PostActivity
import com.h.pixeldroid.R
@ -26,6 +28,12 @@ import com.h.pixeldroid.objects.DiscoverPosts
import com.h.pixeldroid.objects.Status
import com.h.pixeldroid.utils.DBUtils
import com.h.pixeldroid.utils.ImageConverter
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.padding
import com.mikepenz.iconics.utils.paddingDp
import com.mikepenz.iconics.utils.sizeDp
import kotlinx.android.synthetic.main.fragment_search.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
@ -55,22 +63,30 @@ class SearchDiscoverFragment : Fragment() {
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_search, container, false)
val button = view.findViewById<Button>(R.id.searchButton)
val search = view.findViewById<EditText>(R.id.searchEditText)
val search = view.findViewById<SearchView>(R.id.search)
(requireActivity().application as Pixeldroid).getAppComponent().inject(this)
button.setOnClickListener {
val intent = Intent(context, SearchActivity::class.java)
intent.putExtra("searchFeed", search.text.toString())
startActivity(intent)
}
//Configure the search widget (see https://developer.android.com/guide/topics/search/search-dialog#ConfiguringWidget)
val searchManager = requireActivity().getSystemService(Context.SEARCH_SERVICE) as SearchManager
search.setSearchableInfo(searchManager.getSearchableInfo(requireActivity().componentName))
search.isSubmitButtonEnabled = true
// Set posts RecyclerView as a grid with 3 columns
recycler = view.findViewById(R.id.discoverList)
recycler.layoutManager = GridLayoutManager(requireContext(), 3)
adapter = DiscoverRecyclerViewAdapter()
recycler.adapter = adapter
val discoverText = view.findViewById<TextView>(R.id.discoverText)
discoverText.setCompoundDrawables(IconicsDrawable(requireContext(), GoogleMaterial.Icon.gmd_explore).apply {
sizeDp = 24
paddingDp = 20
}, null, null, null)
return view
}

View File

@ -1,5 +1,6 @@
package com.h.pixeldroid.fragments.feeds
import android.annotation.SuppressLint
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.util.Log
@ -43,7 +44,7 @@ open class AccountListFragment : FeedFragment() {
//RequestBuilder that is re-used for every image
profilePicRequest = Glide.with(this)
.asDrawable().apply(RequestOptions().circleCrop())
.asDrawable().dontAnimate().apply(RequestOptions().circleCrop())
.placeholder(R.drawable.ic_default_user)
adapter = AccountsRecyclerViewAdapter()
@ -160,9 +161,11 @@ open class AccountListFragment : FeedFragment() {
override fun onBindViewHolder(holder : ViewHolder, position : Int) {
val account = getItem(position) ?: return
profilePicRequest.load(account.avatar_static).into(holder.avatar)
profilePicRequest.load(account.avatar).into(holder.avatar)
holder.username.text = account.username
@SuppressLint("SetTextI18n")
holder.acct.text = "@${account.acct}"
holder.mView.setOnClickListener { account.openProfile(context) }
}
@ -170,6 +173,7 @@ open class AccountListFragment : FeedFragment() {
inner class ViewHolder(val mView : View) : RecyclerView.ViewHolder(mView) {
val avatar : ImageView = mView.account_entry_avatar
val username : TextView = mView.account_entry_username
val acct: TextView = mView.account_entry_acct
}
override fun getPreloadItems(position : Int) : MutableList<Account> {

View File

@ -117,7 +117,7 @@ open class FeedFragment: Fragment() {
}
/**
* Do nothing here, it is expected to pull to refresh to load newer notifications
* Do nothing here, it is expected to pull to refresh to load newer items
*/
override fun loadBefore(params: LoadParams<ObjectId>, callback: LoadCallback<APIObject>) {}

View File

@ -172,8 +172,8 @@ class OfflineFeedFragment: Fragment() {
holder.itemView.postTabs.visibility = View.VISIBLE
val tabs : ArrayList<ImageFragment> = ArrayList()
//Fill the tabs with each mediaAttachment
for(media in post.media_urls) {
tabs.add(ImageFragment.newInstance(media))
for((index, media) in post.media_urls.withIndex()) {
tabs.add(ImageFragment.newInstance(media, "Photo $index"))
}
holder.itemView.postPager.adapter = object : FragmentStateAdapter(this@OfflineFeedFragment) {
override fun createFragment(position: Int): Fragment {

View File

@ -12,6 +12,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.paging.PagedList
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import at.connyduck.sparkbutton.SparkButton
import com.bumptech.glide.Glide
import com.bumptech.glide.ListPreloader
@ -60,7 +61,7 @@ abstract class PostsFeedFragment : FeedFragment() {
super.onViewCreated(view, savedInstanceState)
val content = makeContent()
content.observe(viewLifecycleOwner,
Observer { c ->
{ c ->
adapter.submitList(c)
//after a refresh is done we need to stop the pull to refresh spinner
swipeRefreshLayout.isRefreshing = false
@ -99,29 +100,12 @@ abstract class PostsFeedFragment : FeedFragment() {
val post = getItem(position) ?: return
val metrics = context.resources.displayMetrics
//Limit the height of the different images
holder.profilePic.maxHeight = metrics.heightPixels
holder.postPic.maxHeight = metrics.heightPixels
holder.postPic.maxHeight = metrics.heightPixels * 3/4
//Setup the post layout
post.setupPost(holder.postView, picRequest, this@PostsFeedFragment, domain, false)
//Set the special HTML text
post.setDescription(holder.postView, api, credential)
//Activate liker
post.activateLiker(holder, api, credential, post.favourited ?: false)
//Activate double tap liking
post.activateDoubleTapLiker(holder, api, credential)
//Show comments
post.showComments(holder, api, credential)
//Activate Commenter
post.activateCommenter(holder, api, credential)
//Activate Reblogger
post.activateReblogger(holder, api ,credential, post.reblogged ?: false)
post.activateButtons(holder, api, credential)
}
override fun getPreloadItems(position: Int): MutableList<Status> {
@ -160,4 +144,7 @@ class PostViewHolder(val postView: View, val context: android.content.Context) :
val postDate : TextView = postView.findViewById(R.id.postDate)
val postDomain : TextView = postView.findViewById(R.id.postDomain)
val sensitiveW : TextView = postView.findViewById(R.id.sensitiveWarning)
val postPager : ViewPager2 = postView.findViewById(R.id.postPager)
val more : ImageButton = postView.findViewById(R.id.status_more)
}

View File

@ -0,0 +1,7 @@
package com.h.pixeldroid.objects
import java.io.Serializable
data class Report(
val id: String
): Serializable

View File

@ -1,29 +1,36 @@
package com.h.pixeldroid.objects
import android.Manifest
import android.app.DownloadManager
import android.content.Context
import android.content.Intent
import android.database.Cursor
import android.graphics.ColorMatrixColorFilter
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Environment
import android.text.Spanned
import android.text.method.LinkMovementMethod
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.*
import androidx.core.content.ContextCompat.startActivity
import androidx.core.net.toUri
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.bumptech.glide.RequestBuilder
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayoutMediator
import com.h.pixeldroid.R
import com.h.pixeldroid.ReportActivity
import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.fragments.ImageFragment
import com.h.pixeldroid.fragments.feeds.postFeeds.PostViewHolder
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
@ -39,8 +46,9 @@ 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 java.io.File
import java.io.Serializable
import java.util.Date
import java.util.*
import kotlin.collections.ArrayList
/*
@ -122,32 +130,63 @@ data class Status(
}
private fun setupPostPics(rootView: View, request: RequestBuilder<Drawable>, homeFragment: Fragment) {
private fun setupPostPics(
rootView: View,
request: RequestBuilder<Drawable>,
homeFragment: Fragment
) {
// 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 {
rootView.sensitiveWarning.visibility = GONE
if(media_attachments?.size == 1) {
request.load(this.getPostUrl()).into(rootView.postPicture)
val imgDescription = media_attachments[0].description.orEmpty().ifEmpty { rootView.context.getString(R.string.no_description) }
rootView.postPicture.contentDescription = imgDescription
rootView.postPicture.setOnLongClickListener {
Snackbar.make(it, imgDescription, Snackbar.LENGTH_SHORT).show()
true
}
} else if(media_attachments?.size!! > 1) {
setupTabsLayout(rootView, request, homeFragment)
}
imagePopUpMenu(rootView, homeFragment.requireActivity())
if (sensitive!!) {
setupSensitiveLayout(rootView)
}
}
private fun setupTabsLayout(rootView: View, request: RequestBuilder<Drawable>, homeFragment: Fragment) {
private fun setupSensitiveLayout(view: View) {
// Set dark layout and warning message
view.sensitiveWarning.visibility = VISIBLE
view.postPicture.colorFilter = ColorMatrixColorFilter(censorColorMatrix())
fun uncensorPicture(view: View) {
view.sensitiveWarning.visibility = GONE
view.postPicture.colorFilter = ColorMatrixColorFilter(uncensorColorMatrix())
}
view.findViewById<TextView>(R.id.sensitiveWarning).setOnClickListener {
uncensorPicture(view)
}
view.findViewById<ImageView>(R.id.postPicture).setOnClickListener {
uncensorPicture(view)
}
}
private fun setupTabsLayout(
rootView: View,
request: RequestBuilder<Drawable>,
homeFragment: Fragment
) {
//Only show the viewPager and tabs
rootView.postPicture.visibility = GONE
rootView.postPager.visibility = VISIBLE
@ -157,7 +196,7 @@ data class Status(
//Fill the tabs with each mediaAttachment
for(media in media_attachments!!) {
tabs.add(ImageFragment.newInstance(media.url!!))
tabs.add(ImageFragment.newInstance(media.url!!, media.description.orEmpty()))
}
setupTabs(tabs, rootView, homeFragment)
@ -234,9 +273,7 @@ data class Status(
}
fun setDescription(rootView: View, api: PixelfedAPI, credential: String) {
val desc = rootView.findViewById<TextView>(R.id.description)
desc.apply {
rootView.findViewById<TextView>(R.id.description).apply {
if (content.isNullOrBlank()) {
visibility = GONE
} else {
@ -246,6 +283,30 @@ data class Status(
}
}
fun activateButtons(holder: PostViewHolder, api: PixelfedAPI, credential: String){
//Set the special HTML text
setDescription(holder.postView, api, credential)
//Activate onclickListeners
activateLiker(
holder, api, credential,
this.favourited ?: false
)
activateReblogger(
holder, api, credential,
this.reblogged ?: false
)
activateCommenter(holder, api, credential)
showComments(holder, api, credential)
//Activate double tap liking
activateDoubleTapLiker(holder, api, credential)
activateMoreButton(holder)
}
fun activateReblogger(
holder: PostViewHolder,
api: PixelfedAPI,
@ -271,6 +332,163 @@ data class Status(
}
}
fun downloadImage(context: Context, url: String, view: View, share: Boolean = false) {
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val downloadUri = Uri.parse(url)
val title = url.substringAfterLast("/")
val request = DownloadManager.Request(downloadUri).apply {
setTitle(title)
if(!share) {
val directory = File(Environment.DIRECTORY_PICTURES)
if (!directory.exists()) {
directory.mkdirs()
}
setDestinationInExternalPublicDir(directory.toString(), title)
}
}
val downloadId = downloadManager.enqueue(request)
val query = DownloadManager.Query().setFilterById(downloadId)
Thread {
var msg = ""
var lastMsg = ""
var downloading = true
while (downloading) {
val cursor: Cursor = downloadManager.query(query)
cursor.moveToFirst()
if (cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
== DownloadManager.STATUS_SUCCESSFUL
) {
downloading = false
}
val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
if (!share) {
msg = when (status) {
DownloadManager.STATUS_FAILED ->
context.getString(R.string.image_download_failed)
DownloadManager.STATUS_RUNNING ->
context.getString(R.string.image_download_downloading)
DownloadManager.STATUS_SUCCESSFUL ->
context.getString(R.string.image_download_success)
else -> ""
}
if (msg != lastMsg && msg != "") {
Snackbar.make(view, msg, Snackbar.LENGTH_SHORT).show()
lastMsg = msg
}
} else if (status == DownloadManager.STATUS_SUCCESSFUL) {
val ext = url.substringAfterLast(".", "*")
val path = cursor.getString(
cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)
)
val file = path.toUri()
val shareIntent: Intent = Intent.createChooser(Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_STREAM, file)
data = file
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
type = "image/$ext"
}, null)
context.startActivity(shareIntent)
}
cursor.close()
}
}.start()
}
fun activateMoreButton(holder: PostViewHolder){
holder.more.setOnClickListener {
PopupMenu(it.context, it).apply {
setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.post_more_menu_report -> {
val intent = Intent(it.context, ReportActivity::class.java)
intent.putExtra(POST_TAG, this@Status)
startActivity(it.context, intent, null)
true
}
R.id.post_more_menu_share_link -> {
val share = Intent.createChooser(Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, uri)
type = "text/plain"
putExtra(Intent.EXTRA_TITLE, content)
}, null)
startActivity(it.context, share, null)
true
}
R.id.post_more_menu_save_to_gallery -> {
Dexter.withContext(holder.context)
.withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.withListener(object : BasePermissionListener() {
override fun onPermissionDenied(p0: PermissionDeniedResponse?) {
Toast.makeText(
holder.context,
holder.context.getString(R.string.write_permission_download_pic),
Toast.LENGTH_SHORT
).show()
}
override fun onPermissionGranted(p0: PermissionGrantedResponse?) {
downloadImage(
holder.context,
media_attachments?.get(holder.postPager.currentItem)?.url
?: "",
holder.postView
)
}
}).check()
true
}
R.id.post_more_menu_share_picture -> {
Dexter.withContext(holder.context)
.withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.withListener(object : BasePermissionListener() {
override fun onPermissionDenied(p0: PermissionDeniedResponse?) {
Toast.makeText(
holder.context,
holder.context.getString(R.string.write_permission_share_pic),
Toast.LENGTH_SHORT
).show()
}
override fun onPermissionGranted(p0: PermissionGrantedResponse?) {
downloadImage(
holder.context,
media_attachments?.get(holder.postPager.currentItem)?.url
?: "",
holder.postView,
share = true,
)
}
}).check()
true
}
else -> false
}
}
inflate(R.menu.post_more_menu)
if(media_attachments.isNullOrEmpty()) {
//make sure to disable image-related things if there aren't any
menu.setGroupVisible(R.id.post_more_group_picture, false)
}
show()
}
}
}
fun activateDoubleTapLiker(
holder: PostViewHolder,
api: PixelfedAPI,
@ -360,12 +578,16 @@ data class Status(
//Toggle comment button
toggleCommentInput(holder)
//Activate commenter
//Activate commenterpostPicture
holder.submitCmnt.setOnClickListener {
val textIn = holder.comment.text
//Open text input
if(textIn.isNullOrEmpty()) {
Toast.makeText(holder.context, holder.context.getString(R.string.empty_comment), Toast.LENGTH_SHORT).show()
Toast.makeText(
holder.context,
holder.context.getString(R.string.empty_comment),
Toast.LENGTH_SHORT
).show()
} else {
//Post the comment
@ -377,78 +599,4 @@ data class Status(
enum class Visibility : Serializable {
public, unlisted, private, direct
}
private fun imagePopUpMenu(view: View, activity: FragmentActivity) {
val anchor = view.findViewById<FrameLayout>(R.id.post_fragment_image_popup_menu_anchor)
if (!media_attachments.isNullOrEmpty() && media_attachments.size == 1) {
view.findViewById<ImageView>(R.id.postPicture).setOnLongClickListener {
PopupMenu(view.context, anchor).apply {
setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.image_popup_menu_save_to_gallery -> {
Dexter.withContext(view.context)
.withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.withListener(object: BasePermissionListener() {
override fun onPermissionDenied(p0: PermissionDeniedResponse?) {
Toast.makeText(view.context, view.context.getString(R.string.write_permission_download_pic), Toast.LENGTH_SHORT).show()
}
override fun onPermissionGranted(p0: PermissionGrantedResponse?) {
downloadImage(activity, getPostUrl()!!)
}
}).check()
true
}
R.id.image_popup_menu_share_picture -> {
Dexter.withContext(view.context)
.withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.withListener(object: BasePermissionListener() {
override fun onPermissionDenied(p0: PermissionDeniedResponse?) {
Toast.makeText(view.context, view.context.getString(R.string.write_permission_share_pic), Toast.LENGTH_SHORT).show()
}
override fun onPermissionGranted(p0: PermissionGrantedResponse?) {
downloadImage(activity, getPostUrl()!!, share = true)
}
}).check()
true
}
else -> false
}
}
inflate(R.menu.image_popup_menu)
show()
}
true
}
}
}
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)
}
}
}

View File

@ -1,99 +0,0 @@
package com.h.pixeldroid.utils
import android.app.DownloadManager
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.database.Cursor
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Environment
import android.provider.MediaStore.Images
import android.widget.Toast
import androidx.fragment.app.FragmentActivity
import com.h.pixeldroid.R
import java.io.File
class ImageUtils {
companion object {
fun downloadImage(activity: FragmentActivity, url: String, share: Boolean = false) {
val context = activity.applicationContext
var msg = ""
var lastMsg = ""
val directory = File(Environment.DIRECTORY_PICTURES)
if (!directory.exists()) {
directory.mkdirs()
}
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE)
as DownloadManager
val downloadUri = Uri.parse(url)
val title = url.substring(url.lastIndexOf("/") + 1)
val ext = url.substring(url.lastIndexOf("."))
val request = DownloadManager.Request(downloadUri).apply {
setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI
or DownloadManager.Request.NETWORK_MOBILE)
setTitle(title)
setDestinationInExternalPublicDir(directory.toString(), title)
}
val downloadId = downloadManager.enqueue(request)
val query = DownloadManager.Query().setFilterById(downloadId)
Thread(Runnable {
var downloading = true
while (downloading) {
val cursor: Cursor = downloadManager.query(query)
cursor.moveToFirst()
if (cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
== DownloadManager.STATUS_SUCCESSFUL) {
downloading = false
}
val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
if(!share) {
msg = when (status) {
DownloadManager.STATUS_FAILED ->
context.getString(R.string.image_download_failed)
DownloadManager.STATUS_RUNNING ->
context.getString(R.string.image_download_downloading)
DownloadManager.STATUS_SUCCESSFUL ->
context.getString(R.string.image_download_success)
else -> ""
}
if (msg != lastMsg && msg != "") {
activity.runOnUiThread {
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
}
lastMsg = msg
}
} else if (status == DownloadManager.STATUS_SUCCESSFUL) {
val icon: Bitmap = BitmapFactory.decodeFile(
Uri.parse(cursor.getString(
cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)
)).path
)
val intentShare = Intent(Intent.ACTION_SEND)
intentShare.type = "image/$ext"
val values = ContentValues()
values.put(Images.Media.TITLE, title)
values.put(Images.Media.MIME_TYPE, "image/$ext")
val uri: Uri = context.contentResolver.insert(
Images.Media.EXTERNAL_CONTENT_URI,
values
)!!
try {
val outstream = context.contentResolver.openOutputStream(uri)!!
icon.compress(Bitmap.CompressFormat.JPEG, 100, outstream)
outstream.close()
} catch(e: Exception) {
e.printStackTrace()
}
intentShare.putExtra(Intent.EXTRA_STREAM, uri)
activity.startActivity(Intent.createChooser(intentShare, context.getString(R.string.share_image)))
}
cursor.close()
}
}).start()
}
}
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M19,7v2.99s-1.99,0.01 -2,0L17,7h-3s0.01,-1.99 0,-2h3L17,2h2v3h3v2h-3zM16,11L16,8h-3L13,5L5,5c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-8h-3zM5,19l3,-4 2,3 3,-4 4,5L5,19z"
android:fillColor="#FFFFFF"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M19,7v2.99s-1.99,0.01 -2,0L17,7h-3s0.01,-1.99 0,-2h3L17,2h2v3h3v2h-3zM16,11L16,8h-3L13,5L5,5c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-8h-3zM5,19l3,-4 2,3 3,-4 4,5L5,19z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2z"
android:fillColor="#000000"/>
</vector>

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
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">
<ImageView
android:id="@+id/account_entry_avatar"
@ -11,20 +11,31 @@
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:contentDescription="TODO"
android:scaleType="centerCrop"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_default_user"
android:contentDescription="TODO" />
tools:src="@drawable/ic_default_user" />
<TextView
android:id="@+id/account_entry_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="28dp"
android:text="TextView"
android:textStyle="bold"
app:layout_constraintStart_toEndOf="@+id/account_entry_avatar"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="@+id/account_entry_avatar"
tools:text="Username" />
<TextView
android:id="@+id/account_entry_acct"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
app:layout_constraintBottom_toBottomOf="@+id/account_entry_avatar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/account_entry_username"
app:layout_constraintTop_toBottomOf="@id/account_entry_username"
tools:text="\@username@domain.tld" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CameraActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/camera_activity_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -10,22 +10,27 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main_activity_main_linear_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/main_toolbar"
<LinearLayout
android:id="@+id/main_activity_main_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimaryTab"
app:contentInsetStartWithNavigation="0dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:navigationContentDescription="Open drawer menu"
app:navigationIcon="@drawable/ic_baseline_menu_24">
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent">
<ImageButton
android:id="@+id/main_drawer_button"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@color/colorPrimaryTab"
android:contentDescription="@string/open_drawer_menu"
android:padding="12dp"
android:src="@drawable/ic_baseline_menu_24" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
@ -36,20 +41,18 @@
app:tabMaxWidth="0dp"
app:tabMode="fixed"
app:tabUnboundedRipple="false" />
</androidx.appcompat.widget.Toolbar>
</LinearLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
app:layout_constraintBottom_toTopOf="@+id/main_toolbar"
app:layout_constraintBottom_toTopOf="@+id/main_activity_main_linear_layout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<com.mikepenz.materialdrawer.widget.MaterialDrawerSliderView

View File

@ -51,7 +51,7 @@
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Guideline
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -84,14 +84,4 @@
app:layout_constraintRight_toRightOf="@+id/right_guideline"
app:layout_constraintBottom_toBottomOf="@+id/bottom_guideline" />
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimaryActionBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -17,15 +17,19 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/postFragmentSingle"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" >
</androidx.fragment.app.FragmentContainerView>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -42,17 +42,29 @@
</androidx.constraintlayout.widget.ConstraintLayout>
<com.mikepenz.iconics.view.IconicsTextView
android:id="@+id/upload_completed_textview"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:text="@string/media_upload_completed"
android:textColor="@android:color/holo_green_light"
android:textSize="16sp"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/image_grid"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
android:padding="16dp"
app:layout_constraintBottom_toTopOf="@id/postTextInputLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
>
</androidx.recyclerview.widget.RecyclerView>
app:layout_constraintTop_toBottomOf="@id/upload_completed_textview" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/buttonConstraints"
@ -61,7 +73,7 @@
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/textInputLayout2">
app:layout_constraintTop_toTopOf="@id/postTextInputLayout">
<Button
android:id="@+id/post_creation_send_button"
@ -70,7 +82,7 @@
android:backgroundTint="@color/colorButtonBg"
android:enabled="false"
android:visibility="gone"
android:text="@string/send"
android:text="@string/post"
android:textColor="@color/colorButtonText"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@ -93,12 +105,12 @@
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/textInputLayout2"
app:layout_constraintBottom_toTopOf="@id/postTextInputLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputLayout2"
android:id="@+id/postTextInputLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/description"
@ -121,16 +133,4 @@
</com.google.android.material.textfield.TextInputLayout>
<com.mikepenz.iconics.view.IconicsTextView
android:id="@+id/upload_completed_textview"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:text="@string/media_upload_completed"
android:textColor="@android:color/holo_green_light"
android:textSize="16sp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -6,7 +6,7 @@
android:layout_height="match_parent"
tools:context=".ProfileActivity">
<ScrollView
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -165,10 +165,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:nestedScrollingEnabled="false"
app:layoutManager="LinearLayoutManager"
tools:context=".fragments.ProfileFragment"
tools:listitem="@layout/fragment_profile_posts" />
</LinearLayout>
</ScrollView>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ReportActivity">
<TextView
android:id="@+id/report_target_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/textInputLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Reporting @user's post:" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:counterEnabled="true"
app:counterMaxLength="1000"
app:layout_constraintBottom_toTopOf="@+id/reportButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/optional_report_comment"
android:inputType="text|textCapSentences|textMultiLine"
android:lines="8" />
</com.google.android.material.textfield.TextInputLayout>
<com.mikepenz.iconics.view.IconicsButton
android:id="@+id/reportButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/report"
android:textColor="@color/colorText"
android:backgroundTint="@color/colorPrimary"
app:iconGravity="end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.75" />
<ProgressBar
android:id="@+id/reportProgressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/reportButton"
app:layout_constraintEnd_toEndOf="@+id/reportButton"
app:layout_constraintStart_toStartOf="@+id/reportButton"
app:layout_constraintTop_toTopOf="@+id/reportButton" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<com.h.pixeldroid.utils.SquareLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foreground="?selectableItemBackground"
android:clickable="true"
android:focusable="true">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerInParent="true"
android:layout_centerVertical="true"
android:background="@drawable/add_photo_alternate_black_24dp" />
</com.h.pixeldroid.utils.SquareLayout>

View File

@ -11,7 +11,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:contentDescription="TODO" />
tools:ignore="ContentDescription" />
</FrameLayout>

View File

@ -1,24 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.flexbox.FlexboxLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.gridlayout.widget.GridLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:flexWrap="wrap"
app:justifyContent="center">
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp">
app:columnCount="3">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/postPreview"
android:layout_width="100dp"
android:layout_height="100dp"
android:contentDescription="TODO" />
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:contentDescription="TODO"
android:padding="1dp"
app:layout_gravity="fill"
app:layout_rowWeight="1"
app:layout_columnWeight="1"/>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.gridlayout.widget.GridLayout>
</com.google.android.flexbox.FlexboxLayout>

View File

@ -1,50 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal">
<com.google.android.material.textfield.TextInputLayout
<androidx.appcompat.widget.SearchView
android:id="@+id/search"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:gravity="center"
android:hint="@string/search"
app:errorEnabled="true"
app:layout_constraintEnd_toStartOf="@+id/searchButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/searchEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:imeOptions="actionDone"
android:inputType="textUri" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/searchButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:backgroundTint="@color/colorButtonBg"
android:text="@string/search"
android:textColor="@color/colorButtonText"
app:layout_constraintBottom_toBottomOf="@+id/search"
app:iconifiedByDefault="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/search" />
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:queryHint="@string/search" />
<ProgressBar
android:id="@+id/discoverProgressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@ -54,21 +32,33 @@
android:id="@+id/discoverRefreshLayout"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
android:layout_marginTop="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/search">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/discoverText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="@string/discover"
android:layout_gravity="center_horizontal"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/discoverList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="com.google.android.flexbox.FlexboxLayoutManager"
app:layout_constraintTop_toBottomOf="@+id/search" />
android:nestedScrollingEnabled="false"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,23 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<com.h.pixeldroid.utils.SquareLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foreground="?selectableItemBackground"
android:clickable="true"
android:focusable="true">
<RelativeLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/galleryImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
android:scaleType="centerCrop" />
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@drawable/ic_baseline_edit_24"/>
</RelativeLayout>
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:background="@drawable/circle_black_24dp"
android:backgroundTint="#7A3E3C3C"
android:foreground="@drawable/ic_baseline_edit_24"
android:foregroundGravity="center"
android:foregroundTint="#FFFFFF"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.h.pixeldroid.utils.SquareLayout>

View File

@ -4,17 +4,10 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:sparkbutton="http://schemas.android.com/apk/res-auto"
tools:context=".fragments.PostFragment">
<androidx.cardview.widget.CardView
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
xmlns:sparkbutton="http://schemas.android.com/apk/res-auto"
tools:context=".fragments.PostFragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
@ -33,7 +26,8 @@
android:layout_marginEnd="10dp"
android:src="@drawable/ic_default_user"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent"
android:contentDescription="@string/profile_picture" />
<TextView
android:id="@+id/username"
@ -42,7 +36,8 @@
android:layout_marginStart="12dp"
app:layout_constraintBottom_toBottomOf="@+id/profilePic"
app:layout_constraintStart_toEndOf="@+id/profilePic"
app:layout_constraintTop_toTopOf="@+id/profilePic"/>
app:layout_constraintTop_toTopOf="@+id/profilePic"
tools:text="username" />
<TextView
android:id="@+id/postDomain"
@ -51,14 +46,14 @@
android:textColor="#b3b3b3"
app:layout_constraintBottom_toBottomOf="@+id/profilePic"
app:layout_constraintStart_toEndOf="@+id/username"
app:layout_constraintTop_toTopOf="@+id/profilePic"/>
app:layout_constraintTop_toTopOf="@+id/profilePic"
tools:text=" from domain.tld" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/postConstraint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:adjustViewBounds="true"
app:layout_constraintTop_toBottomOf="@+id/profilePic">
<androidx.viewpager2.widget.ViewPager2
@ -87,7 +82,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@color/browser_actions_bg_grey"
android:longClickable="true" />
tools:ignore="ContentDescription" />
<FrameLayout
android:id="@+id/post_fragment_image_popup_menu_anchor"
@ -106,11 +101,13 @@
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"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/postPicture"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/postTabs"
tools:src="@color/browser_actions_bg_grey" />
@ -121,7 +118,6 @@
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"
@ -137,7 +133,6 @@
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"
@ -154,7 +149,6 @@
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"
@ -166,6 +160,17 @@
sparkbutton:primaryColor="@color/share_blue"
sparkbutton:secondaryColor="@color/black"/>
<ImageButton
android:id="@+id/status_more"
android:layout_width="24dp"
android:layout_height="30dp"
android:layout_marginEnd="16dp"
android:contentDescription="@string/status_more_options"
android:padding="4dp"
style="?android:attr/actionOverflowButtonStyle"
app:layout_constraintBottom_toBottomOf="@+id/postDomain"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/postDomain" />
<TextView
android:id="@+id/nlikes"
@ -182,7 +187,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="50"
android:gravity="right"
android:gravity="end"
app:layout_constraintEnd_toEndOf="@+id/reblogger"
app:layout_constraintStart_toStartOf="@+id/reblogger"
app:layout_constraintTop_toBottomOf="@+id/reblogger"
@ -214,7 +219,7 @@
android:textColor="#b3b3b3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/description"
tools:text="time" />
tools:text="Yesterday" />
<LinearLayout
android:id="@+id/commentIn"
@ -234,7 +239,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/comment"
android:importantForAutofill="no"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
@ -262,15 +266,10 @@
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginBottom="10dp"
tools:text="TextView"/>
tools:text="3 comments"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.cardview.widget.CardView>
</FrameLayout>

View File

@ -1,18 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/filter_name"
android:text="FILTER_NAME"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
tools:text="FILTER_NAME" />
<ImageView
android:id="@+id/thumbnail"
@ -20,5 +21,5 @@
android:adjustViewBounds="true"
android:layout_width="80dp"
android:layout_height="80dp"
android:contentDescription="thumbnail of filter" />
android:contentDescription="Thumbnail of filter" />
</LinearLayout>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/image_popup_menu_save_to_gallery"
android:title="@string/save_to_gallery"/>
<item android:id="@+id/image_popup_menu_share_picture"
android:title="@string/share_picture"/>
</menu>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/post_more_menu_report"
android:title="@string/report" />
<item android:id="@+id/post_more_menu_share_link"
android:title="@string/share_link" />
<!-- Group that should only be shown if there are pictures in the post -->
<group android:id="@+id/post_more_group_picture">
<item android:id="@+id/post_more_menu_share_picture"
android:title="@string/share_picture"/>
<item android:id="@+id/post_more_menu_save_to_gallery"
android:title="@string/save_to_gallery"/>
</group>
</menu>

View File

@ -15,7 +15,7 @@
<string name="auth_failed">فشلت المصادقة</string>
<string name="followed_notification">يتابعك %1$s</string>
<string name="description">الوصف…</string>
<string name="send">ارسل</string>
<string name="post">ارسل</string>
<string name="whats_an_instance">ماذا نعني بمثيل الخادم؟</string>
<string name="logout">الخروج</string>
<string name="tab_filters">الفلاتر</string>

View File

@ -19,7 +19,7 @@
<string name="save_to_gallery">Desar a la galeria…</string>
<string name="logout">Tancar sessió</string>
<string name="whats_an_instance">Què és una instància\?</string>
<string name="send">enviar</string>
<string name="post">enviar</string>
<string name="description">Descripció…</string>
<string name="liked_notification">%1$s ha dona\'t m\'agrada a la teva publicació</string>
<string name="theme_title">Tema de l\'aplicació</string>

View File

@ -13,7 +13,7 @@
<string name="mention_notification">%1$s hat dich erwähnt</string>
<string name="shared_notification">%1$s hat deinen Beitrag geteilt</string>
<string name="liked_notification">%1$s hat deinen Beitrag favorisiert</string>
<string name="send">senden</string>
<string name="post">senden</string>
<string name="whats_an_instance">Was ist eine Instanz\?</string>
<string name="theme_title">Erscheinungsbild</string>
<string name="description">Beschreibung…</string>

View File

@ -12,7 +12,7 @@
<string name="shared_notification">%1$s compartió tu publicación</string>
<string name="liked_notification">%1$s le gustó tu publicación</string>
<string name="description">Descripción…</string>
<string name="send">enviar</string>
<string name="post">enviar</string>
<string name="whats_an_instance">¿Qué es una instancia\?</string>
<string name="logout">Cerrar sesión</string>
<string name="save_to_gallery">Guardar en la galería…</string>

View File

@ -9,7 +9,7 @@
<string name="save_to_gallery">Gorde galerian…</string>
<string name="logout">Saioa Itxi</string>
<string name="whats_an_instance">Zer da instantzia bat\?</string>
<string name="send">bidali</string>
<string name="post">bidali</string>
<string name="description">Deskribapena…</string>
<string name="mention_notification">%1$s erabiltzaileak aipatu zaitu</string>
<string name="followed_notification">%1$s jarraitzen hasi zaizu</string>

View File

@ -16,7 +16,7 @@
<string name="shared_notification">%1$s مطلب شما را هم‌رسانی کرد</string>
<string name="liked_notification">%1$s مطلب شما را پسندید</string>
<string name="description">توضیحات…</string>
<string name="send">بفرست</string>
<string name="post">بفرست</string>
<string name="whats_an_instance">یک نمونه چیست؟</string>
<string name="logout">خروج</string>
<string name="lbl_brightness">روشنایی</string>

View File

@ -9,7 +9,7 @@
<string name="shared_notification">%1$s a partagé votre publication</string>
<string name="liked_notification">%1$s a aimé votre publication</string>
<string name="description">Description…</string>
<string name="send">envoyer</string>
<string name="post">envoyer</string>
<string name="whats_an_instance">Qu\'est-ce qu\'une instance \?</string>
<string name="logout">Se déconnecter</string>
<string name="save_to_gallery">Sauvegarder dans la galerie…</string>

View File

@ -14,7 +14,7 @@
<string name="liked_notification">a %1$s gustoulle a publicación</string>
<string name="shared_notification">%1$s comparteu a publicación</string>
<string name="description">Descrición…</string>
<string name="send">enviar</string>
<string name="post">enviar</string>
<string name="whats_an_instance">Que é unha instancia\?</string>
<string name="logout">Saír</string>
<string name="lbl_brightness">BRILLO</string>

View File

@ -14,7 +14,7 @@
<string name="mention_notification">%1$s ti ha menzionato</string>
<string name="liked_notification">%1$s è piaciuto il tuo post</string>
<string name="tab_filters">FILTRI</string>
<string name="send">invia</string>
<string name="post">invia</string>
<string name="lbl_brightness">LUMINOSITÀ</string>
<string name="description">Descrizione…</string>
<string name="whats_an_instance">Cos\'è un\'istanza\?</string>

View File

@ -11,7 +11,7 @@
<string name="followed_notification">%1$s さんにフォローされました</string>
<string name="liked_notification">%1$s さんがあなたの投稿をお気に入りに登録しました</string>
<string name="description">説明…</string>
<string name="send">送信</string>
<string name="post">送信</string>
<string name="whats_an_instance">インスタンスとは?</string>
<string name="logout">ログアウト</string>
<string name="lbl_brightness">輝度</string>

View File

@ -11,7 +11,7 @@
<string name="menu_account">Mijn profiel</string>
<string name="logout">Log uit</string>
<string name="whats_an_instance">Wat is een instance\?</string>
<string name="send">stuur</string>
<string name="post">stuur</string>
<string name="description">Beschrijving…</string>
<string name="liked_notification">%1$s vond je bericht leuk</string>
<string name="shared_notification">%1$s heeft je bericht gedeeld</string>

View File

@ -30,7 +30,7 @@
<string name="lbl_brightness">BRILHO</string>
<string name="logout">Sair</string>
<string name="whats_an_instance">O que é uma instância\?</string>
<string name="send">enviar</string>
<string name="post">enviar</string>
<string name="description">Descrição…</string>
<string name="liked_notification">%1$s curtiu o seu post</string>
<string name="shared_notification">%1$s compartilhou o seu post</string>

View File

@ -8,7 +8,7 @@
<string name="followed_notification">%1$s подписался(-лась) на вас</string>
<string name="mention_notification">%1$s упомянул(а) вас</string>
<string name="shared_notification">%1$s поделился(-лась) вашим постом</string>
<string name="send">отправить</string>
<string name="post">отправить</string>
<string name="logout">Выйти</string>
<string name="lbl_brightness">ЯРКОСТЬ</string>
<string name="whats_an_instance">Что такое инстанс\?</string>

View File

@ -22,7 +22,7 @@
<string name="shared_notification">%1$s delande ditt inlägg</string>
<string name="liked_notification">%1$s gillar ditt inlägg</string>
<string name="description">Beskrivning…</string>
<string name="send">skicka</string>
<string name="post">skicka</string>
<string name="whats_an_instance">Vad är en instans\?</string>
<string name="logout">Logga ut</string>
<string name="lbl_brightness">LJUSSTYRKA</string>

View File

@ -9,7 +9,7 @@
<string name="theme_header">Тема</string>
<string name="mention_notification">%1$s згадав(-ла) вас</string>
<string name="description">Опис…</string>
<string name="send">відправити</string>
<string name="post">відправити</string>
<string name="lbl_brightness">Яскравість</string>
<string name="lbl_contrast">Контраст</string>
<string name="lbl_saturation">Насичення</string>

View File

@ -9,7 +9,7 @@
<string name="auth_failed">无法验证</string>
<string name="token_error">获取验证令牌时出错</string>
<string name="theme_header">主题</string>
<string name="send">发送</string>
<string name="post">发送</string>
<string name="logout">注销</string>
<string name="app_name">PixelDroid</string>
<string name="title_activity_settings2">配置</string>

View File

@ -44,8 +44,9 @@
<string name="upload_post_success">Post uploaded successfully</string>
<string name="upload_post_error">Post upload failed</string>
<string name="description">Description…</string>
<string name="send">send</string>
<!-- Post edition -->
<string name="post">post</string>
<string name="add_photo">Add a photo</string>
<!-- Post editing -->
<string name="lbl_brightness">BRIGHTNESS</string>
<string name="lbl_contrast">CONTRAST</string>
<string name="lbl_saturation">SATURATION</string>
@ -121,6 +122,19 @@
<string name="post_title">%1$s\'s post</string>
<string name="followers_title">%1$s\'s followers</string>
<string name="follows_title">%1$s\'s follows</string>
<string name="search_empty_error">Search query can\'t be empty</string>
<string name="status_more_options">More options</string>
<string name="report">Report</string>
<string name="share_link">Share Link</string>
<string name="optional_report_comment">Optional message for mods/admins</string>
<string name="report_target">Report @%1$s\'s post</string>
<!-- Text on button, shown when report was successful. {gmd_check_circle} is an icon, position it as is appropriate in target language -->
<string name="reported">Reported {gmd_check_circle}</string>
<string name="report_error">Could not send report</string>
<string name="toolbar_title_edit">Edit</string>
<string name="profile_picture">Profile picture</string>
<string name="open_drawer_menu">Open drawer menu</string>
<string name="discover">DISCOVER</string>
</resources>

View File

@ -24,6 +24,5 @@
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
</resources>

View File

@ -3,7 +3,7 @@
<PreferenceCategory app:title="@string/theme_header">
<ListPreference
app:defaultValue="@string/default_theme"
app:defaultValue="default"
app:entries="@array/theme_entries"
app:entryValues="@array/theme_values"
app:key="theme"
@ -13,5 +13,9 @@
<Preference android:title="@string/about"
android:key="about"
android:summary="@string/about_pixeldroid"/>
android:summary="@string/about_pixeldroid">
<intent
android:targetPackage="com.h.pixeldroid"
android:targetClass="com.h.pixeldroid.AboutActivity"/>
</Preference>
</PreferenceScreen>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/app_name"
android:hint="Search" >
android:hint="@string/search" >
</searchable>