diff --git a/app/build.gradle b/app/build.gradle index 886a7eca..73d192c4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -30,6 +30,8 @@ android { androidTest.java.srcDirs += 'src/androidTest/java' } + + buildTypes { debug { testCoverageEnabled true @@ -70,9 +72,13 @@ dependencies { implementation "androidx.room:room-runtime:$room_version" implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" + implementation 'androidx.navigation:navigation-fragment-ktx:2.2.2' + implementation 'androidx.navigation:navigation-ui-ktx:2.2.2' kapt "androidx.room:room-compiler:$room_version" implementation "androidx.room:room-ktx:$room_version" + implementation 'info.androidhive:imagefilters:1.0.7' + implementation("com.github.bumptech.glide:glide:4.11.0") { exclude group: "com.android.support" } diff --git a/app/src/androidTest/java/com/h/pixeldroid/EditPhotoTest.kt b/app/src/androidTest/java/com/h/pixeldroid/EditPhotoTest.kt new file mode 100644 index 00000000..9b8ccaa3 --- /dev/null +++ b/app/src/androidTest/java/com/h/pixeldroid/EditPhotoTest.kt @@ -0,0 +1,156 @@ +package com.h.pixeldroid + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.view.View +import android.widget.SeekBar +import androidx.test.core.app.ActivityScenario +import androidx.test.espresso.Espresso +import androidx.test.espresso.PerformException +import androidx.test.espresso.UiController +import androidx.test.espresso.ViewAction +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.matcher.ViewMatchers.* +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.rule.GrantPermissionRule +import com.google.android.material.tabs.TabLayout +import com.h.pixeldroid.adapters.ThumbnailAdapter +import com.h.pixeldroid.testUtility.CustomMatchers +import com.h.pixeldroid.testUtility.MockServer +import kotlinx.android.synthetic.main.fragment_edit_image.* +import org.hamcrest.CoreMatchers.allOf +import org.junit.Assert +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.Timeout +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class EditPhotoTest { + + private val mockServer = MockServer() + private lateinit var activity: PhotoEditActivity + private lateinit var activityScenario: ActivityScenario + + @get:Rule + var globalTimeout: Timeout = Timeout.seconds(100) + + @get:Rule + var mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) + + @Before + fun before() { + val context = InstrumentationRegistry.getInstrumentation().targetContext + mockServer.start() + val baseUrl = mockServer.getUrl() + val preferences = context.getSharedPreferences("com.h.pixeldroid.pref", Context.MODE_PRIVATE) + preferences.edit().putString("accessToken", "azerty").apply() + preferences.edit().putString("domain", baseUrl.toString()).apply() + + // Launch PhotoEditActivity + val uri: Uri = Uri.parse("android.resource://com.h.pixeldroid/drawable/index") + val intent = Intent(context, PhotoEditActivity::class.java).putExtra("uri", uri) + + activityScenario = ActivityScenario.launch(intent).onActivity{a -> activity = a} + + Thread.sleep(1000) + } + + private fun selectTabAtPosition(tabIndex: Int): ViewAction { + return object : ViewAction { + override fun getDescription() = "with tab at index $tabIndex" + + override fun getConstraints() = + allOf(isDisplayed(), isAssignableFrom(TabLayout::class.java)) + + override fun perform(uiController: UiController, view: View) { + val tabLayout = view as TabLayout + val tabAtIndex: TabLayout.Tab = tabLayout.getTabAt(tabIndex) + ?: throw PerformException.Builder() + .withCause(Throwable("No tab at index $tabIndex")) + .build() + + tabAtIndex.select() + } + } + } + + private fun setProgress(progress: Int): ViewAction? { + return object : ViewAction { + override fun getDescription() = "Set the progress on a SeekBar" + + override fun getConstraints() = isAssignableFrom(SeekBar::class.java) + + override fun perform(uiController: UiController, view: View) { + val seekBar = view as SeekBar + seekBar.progress = progress + } + } + } + + @Test + fun PhotoEditActivityHasAnImagePreview() { + Espresso.onView(withId(R.id.image_preview)).check(matches(isDisplayed())) + } + + @Test + fun FiltersIsSwipeableAndClickeable() { + Espresso.onView(withId(R.id.recycler_view)) + .perform(actionOnItemAtPosition(1, CustomMatchers.clickChildViewWithId(R.id.thumbnail))) + Thread.sleep(1000) + Espresso.onView(withId(R.id.recycler_view)) + .perform(actionOnItemAtPosition(1, CustomMatchers.slowSwipeLeft(false))) + Thread.sleep(1000) + Espresso.onView(withId(R.id.recycler_view)) + .perform(actionOnItemAtPosition(5, CustomMatchers.clickChildViewWithId(R.id.thumbnail))) + } + + @Test + fun BirghtnessSaturationContrastTest() { + Espresso.onView(withId(R.id.tabs)).perform(selectTabAtPosition(1)) + + Thread.sleep(1000) + + var change = 5 + Espresso.onView(withId(R.id.seekbar_brightness)).perform(setProgress(change)) + Espresso.onView(withId(R.id.seekbar_contrast)).perform(setProgress(change)) + Espresso.onView(withId(R.id.seekbar_saturation)).perform(setProgress(change)) + + Assert.assertEquals(change, activity.seekbar_brightness.progress) + Assert.assertEquals(change, activity.seekbar_contrast.progress) + Assert.assertEquals(change, activity.seekbar_saturation.progress) + + Thread.sleep(1000) + + change = 15 + Espresso.onView(withId(R.id.seekbar_brightness)).perform(setProgress(change)) + Espresso.onView(withId(R.id.seekbar_contrast)).perform(setProgress(change)) + Espresso.onView(withId(R.id.seekbar_saturation)).perform(setProgress(change)) + + Assert.assertEquals(change, activity.seekbar_brightness.progress) + Assert.assertEquals(change, activity.seekbar_contrast.progress) + Assert.assertEquals(change, activity.seekbar_saturation.progress) + } + + @Test + fun SaveButton() { + Espresso.onView(withId(R.id.toolbar)).check(matches(isDisplayed())); + Espresso.onView(withId(R.id.action_save)).perform(click()) + Espresso.onView(withId(com.google.android.material.R.id.snackbar_text)) + .check(matches(withText("Image succesfully saved"))) + + } + + @Test + fun buttonUploadLaunchNewPostActivity() { + Espresso.onView(withId(R.id.action_upload)).perform(click()) + Thread.sleep(1000) + Espresso.onView(withId(R.id.post_creation_picture_frame)).check(matches(isDisplayed())) + } + +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/h/pixeldroid/testUtility/CustomMatchers.kt b/app/src/androidTest/java/com/h/pixeldroid/testUtility/CustomMatchers.kt index b5a69db8..e2234b7f 100644 --- a/app/src/androidTest/java/com/h/pixeldroid/testUtility/CustomMatchers.kt +++ b/app/src/androidTest/java/com/h/pixeldroid/testUtility/CustomMatchers.kt @@ -46,6 +46,21 @@ abstract class CustomMatchers { ) } + /** + * @param percent can be 1 or 0 + * 1: swipes all the way left + * 0: swipes half way left + */ + fun slowSwipeLeft(percent: Boolean) : ViewAction { + return ViewActions.actionWithAssertions( + GeneralSwipeAction( + Swipe.SLOW, + GeneralLocation.CENTER_RIGHT, + if(percent) GeneralLocation.CENTER_LEFT else GeneralLocation.CENTER, + Press.FINGER) + ) + } + fun getText(matcher: Matcher?): String? { val stringHolder = arrayOf(null) Espresso.onView(matcher).perform(object : ViewAction { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 70f8df02..4155d221 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,8 +4,10 @@ package="com.h.pixeldroid"> + + + + + @@ -68,6 +75,10 @@ android:scheme="@string/auth_scheme" /> + + { + super.onBackPressed() + } + R.id.action_upload -> { + saveImageToGallery(false) + } + R.id.action_save -> { + saveImageToGallery(true) + return true + } + } + + return super.onOptionsItemSelected(item) + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + if(grantResults.size > 1 + && grantResults[0] == PackageManager.PERMISSION_GRANTED + && grantResults[1] == PackageManager.PERMISSION_GRANTED) { + // permission was granted + when (requestCode) { + REQUEST_CODE_PERMISSIONS_SAVE_PHOTO -> permissionsGrantedToSave(true) + REQUEST_CODE_PERMISSIONS_SEND_PHOTO -> permissionsGrantedToSave(false) + } + } else { + Snackbar.make(coordinator_edit, "Permission denied", Snackbar.LENGTH_LONG).show() + } + } + + private fun uploadImage(file: File) { + val intent = Intent (applicationContext, PostCreationActivity::class.java) + intent.putExtra("picture_uri", Uri.fromFile(file)) + //file.delete() + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + applicationContext!!.startActivity(intent) + } + + private fun saveImageToGallery(save: Boolean) { + // runtime permission and process + if (!allPermissionsGranted()) { + ActivityCompat.requestPermissions( + this, + REQUIRED_PERMISSIONS, + if(save) REQUEST_CODE_PERMISSIONS_SAVE_PHOTO else REQUEST_CODE_PERMISSIONS_SEND_PHOTO + ) + } else { + permissionsGrantedToSave(save) + } + } + + /** + * Check if all permission specified in the manifest have been granted + */ + private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all { + ContextCompat.checkSelfPermission( + applicationContext, it) == PackageManager.PERMISSION_GRANTED + } + + private fun File.writeBitmap(bitmap: Bitmap) { + outputStream().use { out -> + bitmap.compress(Bitmap.CompressFormat.PNG, 85, out) + out.flush() + } + } + + private fun permissionsGrantedToSave(save: Boolean) { + val file = if(!save){ + //put picture in cache + File.createTempFile("temp_img", ".png", cacheDir) + } else{ + // Save the picture (quality is ignored for PNG) + File(outputDirectory, SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.US) + .format(System.currentTimeMillis()) + ".png") + } + try { + file.writeBitmap(finalImage) + } catch (e: IOException) { + Snackbar.make(coordinator_edit, "Unable to save image", Snackbar.LENGTH_LONG).show() + } + + if (!save) { + uploadImage(file) + } else { + Snackbar.make(coordinator_edit, "Image succesfully saved", Snackbar.LENGTH_LONG).show() + } + } + + override fun onFilterSelected(filter: Filter) { + resetControls() + filteredImage = originalImage!!.copy(BITMAP_CONFIG, true) + image_preview.setImageBitmap(filter.processFilter(filteredImage)) + finalImage = filteredImage.copy(BITMAP_CONFIG, true) + } + + private fun resetControls() { + editImageFragment.resetControl() + + brightnessFinal = 0 + saturationFinal = 1.0f + contrastFinal = 1.0f + } + + override fun onBrightnessChange(brightness: Int) { + brightnessFinal = brightness + val myFilter = Filter() + myFilter.addSubFilter(BrightnessSubFilter(brightness)) + image_preview.setImageBitmap(myFilter.processFilter(finalImage.copy(BITMAP_CONFIG, true))) + } + + override fun onSaturationChange(saturation: Float) { + saturationFinal = saturation + val myFilter = Filter() + myFilter.addSubFilter(SaturationSubfilter(saturation)) + image_preview.setImageBitmap(myFilter.processFilter(finalImage.copy(BITMAP_CONFIG, true))) + } + + override fun onContrastChange(contrast: Float) { + contrastFinal = contrast + val myFilter = Filter() + myFilter.addSubFilter(ContrastSubFilter(contrast)) + image_preview.setImageBitmap(myFilter.processFilter(finalImage.copy(BITMAP_CONFIG, true))) + } + + override fun onEditStarted() { + + } + + override fun onEditCompleted() { + val bitmap = filteredImage.copy(BITMAP_CONFIG, true) + val myFilter = Filter() + myFilter.addSubFilter(ContrastSubFilter(contrastFinal)) + myFilter.addSubFilter(SaturationSubfilter(saturationFinal)) + myFilter.addSubFilter(BrightnessSubFilter(brightnessFinal)) + + finalImage = myFilter.processFilter(bitmap) + } +} diff --git a/app/src/main/java/com/h/pixeldroid/PostCreationActivity.kt b/app/src/main/java/com/h/pixeldroid/PostCreationActivity.kt index a07f221d..b3d6ef95 100644 --- a/app/src/main/java/com/h/pixeldroid/PostCreationActivity.kt +++ b/app/src/main/java/com/h/pixeldroid/PostCreationActivity.kt @@ -47,9 +47,7 @@ class PostCreationActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_post_creation) - val imageUri: Uri = intent.getParcelableExtra("picture_uri")!! - saveImage(imageUri) pictureFrame = findViewById(R.id.post_creation_picture_frame) diff --git a/app/src/main/java/com/h/pixeldroid/ProfileActivity.kt b/app/src/main/java/com/h/pixeldroid/ProfileActivity.kt index 46f247d3..1d68ebe4 100644 --- a/app/src/main/java/com/h/pixeldroid/ProfileActivity.kt +++ b/app/src/main/java/com/h/pixeldroid/ProfileActivity.kt @@ -17,7 +17,7 @@ import androidx.core.content.ContextCompat import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import com.h.pixeldroid.api.PixelfedAPI -import com.h.pixeldroid.fragments.ProfilePostsRecyclerViewAdapter +import com.h.pixeldroid.adapters.ProfilePostsRecyclerViewAdapter import com.h.pixeldroid.objects.Account import com.h.pixeldroid.objects.Account.Companion.ACCOUNT_TAG import com.h.pixeldroid.objects.Relationship diff --git a/app/src/main/java/com/h/pixeldroid/adapters/EditPhotoViewPagerAdapter.kt b/app/src/main/java/com/h/pixeldroid/adapters/EditPhotoViewPagerAdapter.kt new file mode 100644 index 00000000..22ed1e43 --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/adapters/EditPhotoViewPagerAdapter.kt @@ -0,0 +1,25 @@ +package com.h.pixeldroid.adapters + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentPagerAdapter + +class EditPhotoViewPagerAdapter (manager: FragmentManager): + FragmentPagerAdapter(manager, FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { + + private val fragmentList = ArrayList() + private val fragmentTitleList = ArrayList() + + override fun getItem(position: Int) = fragmentList[position] + + override fun getCount() = fragmentList.size + + fun addFragment(fragment: Fragment, title: String) { + fragmentList.add(fragment) + fragmentTitleList.add(title) + } + + override fun getPageTitle(position: Int): CharSequence? { + return fragmentTitleList[position] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/h/pixeldroid/fragments/ProfilePostsRecyclerViewAdapter.kt b/app/src/main/java/com/h/pixeldroid/adapters/ProfilePostsRecyclerViewAdapter.kt similarity index 98% rename from app/src/main/java/com/h/pixeldroid/fragments/ProfilePostsRecyclerViewAdapter.kt rename to app/src/main/java/com/h/pixeldroid/adapters/ProfilePostsRecyclerViewAdapter.kt index a41c731f..7d0a4dcf 100644 --- a/app/src/main/java/com/h/pixeldroid/fragments/ProfilePostsRecyclerViewAdapter.kt +++ b/app/src/main/java/com/h/pixeldroid/adapters/ProfilePostsRecyclerViewAdapter.kt @@ -1,4 +1,4 @@ -package com.h.pixeldroid.fragments +package com.h.pixeldroid.adapters import android.content.Intent import androidx.recyclerview.widget.RecyclerView diff --git a/app/src/main/java/com/h/pixeldroid/adapters/ThumbnailAdapter.kt b/app/src/main/java/com/h/pixeldroid/adapters/ThumbnailAdapter.kt new file mode 100644 index 00000000..e80258ce --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/adapters/ThumbnailAdapter.kt @@ -0,0 +1,57 @@ +package com.h.pixeldroid.adapters + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView +import com.h.pixeldroid.R +import com.h.pixeldroid.interfaces.FilterListFragmentListener +import com.zomato.photofilters.utils.ThumbnailItem +import kotlinx.android.synthetic.main.thumbnail_list_item.view.* + +class ThumbnailAdapter (private val context: Context, + private val tbItemList: List, + private val listener: FilterListFragmentListener): RecyclerView.Adapter() { + + private var selectedIndex = 0 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { + val itemView = LayoutInflater.from(context).inflate(R.layout.thumbnail_list_item, parent, false) + return MyViewHolder(itemView) + } + + override fun getItemCount(): Int { + return tbItemList.size + } + + override fun onBindViewHolder(holder: MyViewHolder, position: Int) { + val tbItem = tbItemList[position] + holder.thumbnail.setImageBitmap(tbItem.image) + holder.thumbnail.setOnClickListener { + listener.onFilterSelected(tbItem.filter) + selectedIndex = position + notifyDataSetChanged() + } + + holder.filterName.text = tbItem.filterName + + if(selectedIndex == position) + holder.filterName.setTextColor(ContextCompat.getColor(context, R.color.filterLabelSelected)) + else + holder.filterName.setTextColor(ContextCompat.getColor(context, R.color.filterLabelNormal)) + } + + class MyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { + var thumbnail: ImageView + var filterName: TextView + + init { + thumbnail = itemView.thumbnail + filterName = itemView.filter_name + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/h/pixeldroid/fragments/EditImageFragment.kt b/app/src/main/java/com/h/pixeldroid/fragments/EditImageFragment.kt new file mode 100644 index 00000000..832cd3f9 --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/fragments/EditImageFragment.kt @@ -0,0 +1,89 @@ +package com.h.pixeldroid.fragments + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.SeekBar +import com.h.pixeldroid.R +import com.h.pixeldroid.interfaces.EditImageFragmentListener + +class EditImageFragment : Fragment(), SeekBar.OnSeekBarChangeListener { + + private var listener: EditImageFragmentListener? = null + + private lateinit var seekbarBrightness: SeekBar + private lateinit var seekbarSaturation: SeekBar + private lateinit var seekbarContrast: SeekBar + + private var BRIGHTNESS_START = 100 + private var SATURATION_START = 0 + private var CONTRAST_START = 10 + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + // Inflate the layout for this fragment + val view = inflater.inflate(R.layout.fragment_edit_image, container, false) + + seekbarBrightness = view.findViewById(R.id.seekbar_brightness) + seekbarSaturation = view.findViewById(R.id.seekbar_saturation) + seekbarContrast = view.findViewById(R.id.seekbar_contrast) + + seekbarBrightness.max = 200 + seekbarBrightness.progress = BRIGHTNESS_START + + seekbarContrast.max = 20 + seekbarContrast.progress = CONTRAST_START + + seekbarSaturation.max = 30 + seekbarSaturation.progress = SATURATION_START + + seekbarBrightness.setOnSeekBarChangeListener(this) + seekbarContrast.setOnSeekBarChangeListener(this) + seekbarSaturation.setOnSeekBarChangeListener(this) + + return view + } + + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + var prog = progress + + if(listener != null) { + when(seekBar!!.id) { + R.id.seekbar_brightness -> listener!!.onBrightnessChange(progress - 100) + R.id.seekbar_saturation -> { + prog += 10 + val tempProgress = .10f * prog + listener!!.onSaturationChange(tempProgress) + } + R.id.seekbar_contrast -> { + val tempProgress = .10f * prog + listener!!.onSaturationChange(tempProgress) + } + } + } + } + + fun resetControl() { + seekbarBrightness.progress = BRIGHTNESS_START + seekbarContrast.progress = CONTRAST_START + seekbarSaturation.progress = SATURATION_START + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) { + if(listener != null) + listener!!.onEditStarted() + } + + override fun onStopTrackingTouch(seekBar: SeekBar?) { + if(listener != null) + listener!!.onEditCompleted() + } + + fun setListener(listener: EditImageFragmentListener) { + this.listener = listener + } +} diff --git a/app/src/main/java/com/h/pixeldroid/fragments/FilterListFragment.kt b/app/src/main/java/com/h/pixeldroid/fragments/FilterListFragment.kt new file mode 100644 index 00000000..3878e239 --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/fragments/FilterListFragment.kt @@ -0,0 +1,104 @@ +package com.h.pixeldroid.fragments + +import android.graphics.Bitmap +import android.os.Bundle +import android.provider.MediaStore +import android.util.TypedValue +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.h.pixeldroid.PhotoEditActivity +import com.h.pixeldroid.R +import com.h.pixeldroid.adapters.ThumbnailAdapter +import com.h.pixeldroid.interfaces.FilterListFragmentListener +import com.h.pixeldroid.utils.SpaceItemDecoration +import com.zomato.photofilters.FilterPack +import com.zomato.photofilters.imageprocessors.Filter +import com.zomato.photofilters.utils.ThumbnailItem +import com.zomato.photofilters.utils.ThumbnailsManager + +class FilterListFragment : Fragment(), FilterListFragmentListener { + + internal lateinit var recyclerView: RecyclerView + internal var listener : FilterListFragmentListener? = null + internal lateinit var adapter: ThumbnailAdapter + internal lateinit var tbItemList: MutableList + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + // Inflate the layout for this fragment + val view = inflater.inflate(R.layout.fragment_filter_list, container, false) + + tbItemList = ArrayList() + adapter = ThumbnailAdapter(requireActivity(), tbItemList, this) + + recyclerView = view.findViewById(R.id.recycler_view) + recyclerView.layoutManager = LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false) + recyclerView.itemAnimator = DefaultItemAnimator() + + val space = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f, resources.displayMetrics).toInt() + recyclerView.addItemDecoration(SpaceItemDecoration(space)) + recyclerView.adapter = adapter + + displayImage(null) + + return view + } + + fun displayImage(bitmap: Bitmap?) { + val r = Runnable { + val tbImage: Bitmap? + if (bitmap == null) { + tbImage = MediaStore.Images.Media.getBitmap(requireActivity().contentResolver, PhotoEditActivity.URI.picture_uri) + } else { + tbImage = Bitmap.createScaledBitmap(bitmap, 100, 100, false) + } + + if (tbImage == null) + return@Runnable + + setupFilter(tbImage) + + tbItemList.addAll(ThumbnailsManager.processThumbs(activity)) + requireActivity().runOnUiThread{ adapter.notifyDataSetChanged() } + } + + Thread(r).start() + } + + private fun setupFilter(tbImage: Bitmap?) { + ThumbnailsManager.clearThumbs() + tbItemList.clear() + + val tbItem = ThumbnailItem() + tbItem.image = tbImage + tbItem.filterName = "Normal" + ThumbnailsManager.addThumb(tbItem) + + val filters = FilterPack.getFilterPack(requireActivity()) + + for (filter in filters) { + val item = ThumbnailItem() + item.image = tbImage + item.filter = filter + item.filterName = filter.name + ThumbnailsManager.addThumb(item) + } + } + + override fun onFilterSelected(filter: Filter) { + if(listener != null ){ + listener!!.onFilterSelected(filter) + } + } + + fun setListener(listFragmentListener: FilterListFragmentListener) { + this.listener = listFragmentListener + } +} diff --git a/app/src/main/java/com/h/pixeldroid/fragments/NewPostFragment.kt b/app/src/main/java/com/h/pixeldroid/fragments/NewPostFragment.kt index 96ef5ddf..88af3e46 100644 --- a/app/src/main/java/com/h/pixeldroid/fragments/NewPostFragment.kt +++ b/app/src/main/java/com/h/pixeldroid/fragments/NewPostFragment.kt @@ -2,13 +2,14 @@ package com.h.pixeldroid.fragments import android.app.Activity import android.content.Intent +import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Button import androidx.fragment.app.Fragment -import com.h.pixeldroid.PostCreationActivity +import com.h.pixeldroid.PhotoEditActivity import com.h.pixeldroid.R /** @@ -34,14 +35,21 @@ class NewPostFragment : Fragment() { uploadPicture() } + val takePictureButton: Button = view.findViewById(R.id.takePictureButton) + takePictureButton.setOnClickListener{ + val uri: Uri = Uri.parse("android.resource://com.h.pixeldroid/drawable/index") + val intent = Intent(context, PhotoEditActivity::class.java).putExtra("uri", uri) + startActivity(intent) + } + return view } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (resultCode == Activity.RESULT_OK && data != null && requestCode == PICK_IMAGE_REQUEST && data.data != null) - startActivity(Intent(activity, PostCreationActivity::class.java) - .putExtra("picture_uri", data.data) + startActivity(Intent(activity, PhotoEditActivity::class.java) + .putExtra("uri", data.data) ) } @@ -56,4 +64,4 @@ class NewPostFragment : Fragment() { ) } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/h/pixeldroid/fragments/ProfilePostsFragment.kt b/app/src/main/java/com/h/pixeldroid/fragments/ProfilePostsFragment.kt index c44449bb..a36a8efb 100644 --- a/app/src/main/java/com/h/pixeldroid/fragments/ProfilePostsFragment.kt +++ b/app/src/main/java/com/h/pixeldroid/fragments/ProfilePostsFragment.kt @@ -12,6 +12,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.h.pixeldroid.BuildConfig import com.h.pixeldroid.R +import com.h.pixeldroid.adapters.ProfilePostsRecyclerViewAdapter import com.h.pixeldroid.api.PixelfedAPI diff --git a/app/src/main/java/com/h/pixeldroid/interfaces/EditImageFragmentListener.kt b/app/src/main/java/com/h/pixeldroid/interfaces/EditImageFragmentListener.kt new file mode 100644 index 00000000..3c743123 --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/interfaces/EditImageFragmentListener.kt @@ -0,0 +1,13 @@ +package com.h.pixeldroid.interfaces + +interface EditImageFragmentListener { + fun onBrightnessChange(brightness: Int) + + fun onSaturationChange(saturation: Float) + + fun onContrastChange(contrast: Float) + + fun onEditStarted() + + fun onEditCompleted() +} \ No newline at end of file diff --git a/app/src/main/java/com/h/pixeldroid/interfaces/FilterListFragmentListener.kt b/app/src/main/java/com/h/pixeldroid/interfaces/FilterListFragmentListener.kt new file mode 100644 index 00000000..f0df513e --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/interfaces/FilterListFragmentListener.kt @@ -0,0 +1,7 @@ +package com.h.pixeldroid.interfaces + +import com.zomato.photofilters.imageprocessors.Filter + +interface FilterListFragmentListener { + fun onFilterSelected(filter: Filter) +} \ No newline at end of file diff --git a/app/src/main/java/com/h/pixeldroid/utils/NonSwipeableViewPager.kt b/app/src/main/java/com/h/pixeldroid/utils/NonSwipeableViewPager.kt new file mode 100644 index 00000000..faff962e --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/utils/NonSwipeableViewPager.kt @@ -0,0 +1,43 @@ +package com.h.pixeldroid.utils + +import android.content.Context +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.animation.DecelerateInterpolator +import android.widget.Scroller +import androidx.viewpager.widget.ViewPager + +class NonSwipeableViewPager: ViewPager { + constructor(context: Context):super(context) { + setMyScroller() + } + + constructor(context: Context,attributeSet: AttributeSet): super(context, attributeSet) { + setMyScroller() + } + + override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { + return false + } + + override fun onTouchEvent(ev: MotionEvent?): Boolean { + return false + } + + private fun setMyScroller() { + try { + val viewPager = ViewPager::class.java + val scroller = viewPager.getDeclaredField("mScroller") + scroller.isAccessible = true + scroller.set(this, FilterScroller(context)) + } catch (e:Exception) { + e.printStackTrace() + } + } +} + +class FilterScroller(context: Context): Scroller(context, DecelerateInterpolator()) { + override fun startScroll(startX: Int, startY: Int, dx: Int, dy: Int, duration: Int) { + super.startScroll(startX, startY, dx, dy, 400) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/h/pixeldroid/utils/SpaceItemDecoration.kt b/app/src/main/java/com/h/pixeldroid/utils/SpaceItemDecoration.kt new file mode 100644 index 00000000..3f3dfe0e --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/utils/SpaceItemDecoration.kt @@ -0,0 +1,22 @@ +package com.h.pixeldroid.utils + +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView + +class SpaceItemDecoration(private val space: Int): RecyclerView.ItemDecoration() { + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + if (parent.getChildAdapterPosition(view) == state.itemCount - 1) { + outRect.left = space + outRect.right = 0 + } else { + outRect.left = 0 + outRect.right = space + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_crop_black_24dp.xml b/app/src/main/res/drawable/ic_crop_black_24dp.xml new file mode 100644 index 00000000..5a4749cd --- /dev/null +++ b/app/src/main/res/drawable/ic_crop_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_file_upload_black_24dp.xml b/app/src/main/res/drawable/ic_file_upload_black_24dp.xml new file mode 100644 index 00000000..d6339722 --- /dev/null +++ b/app/src/main/res/drawable/ic_file_upload_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_save_black_24dp.xml b/app/src/main/res/drawable/ic_save_black_24dp.xml new file mode 100644 index 00000000..a561d632 --- /dev/null +++ b/app/src/main/res/drawable/ic_save_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_photo_edit.xml b/app/src/main/res/layout/activity_photo_edit.xml new file mode 100644 index 00000000..a1872ef7 --- /dev/null +++ b/app/src/main/res/layout/activity_photo_edit.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/content_photo_edit.xml b/app/src/main/res/layout/content_photo_edit.xml new file mode 100644 index 00000000..4a6e57a3 --- /dev/null +++ b/app/src/main/res/layout/content_photo_edit.xml @@ -0,0 +1,37 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_camera.xml b/app/src/main/res/layout/fragment_camera.xml new file mode 100644 index 00000000..bf72de19 --- /dev/null +++ b/app/src/main/res/layout/fragment_camera.xml @@ -0,0 +1,39 @@ + + + +