Camera tab (#149)
* added CameraX depedencies * added basis of camera activity layout * basis of camera layout * declare activity and request camera permissions * request camera permissions * implemented basic viewFinder aka stream preview * added drawing of layout * linked camera activity to new post fragment * texture view now fills the entire screen * added button, call to camera activity FOR RESULT and return to New Post Fragment * can now take a photo a upload it * locked camera activity in portrait orientation * added basic test to CameraActivity * added test with device orientation * deleted orientation test as it broke the whole app * deleted occurence of orientation tool for tests * added buttons for flipping cameras and picture upload * replaced NewPostFragment and took its job * deleted cameraActivity * replaced newPostFragment by Camera Activity * revamped NewPostFragment * perform basic test on buttons * corrected retrieval of buttons * robust camera fragment * fix thumbnail permission requests * refactor, finish fragment * try to fix build not working on CI * Add dependency * Fix camera test * update tests, delete obsolete xml * Try to fix test * Stop nullpointer that happens sometimes, when it shouldn't * remove unused strings * edit the photo instead of posting directly * fix test after changing putextra name * remove useless analyzer * add test? * better tests? * add dependencies * update dependencies * try to fix build * undo change to build.gradle * try to fix tests * try to fix tests * remove useless listener * save image so that ci can go through gallery * remove extraneous permission * Any app that declares the WRITE_EXTERNAL_STORAGE permission is implicitly granted this permission. Co-authored-by: Andrea Clement <samuel.dietz@epfl.ch>
This commit is contained in:
parent
5ac3967400
commit
252a192ff3
@ -65,11 +65,11 @@ dependencies {
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.8.1'
|
||||
implementation 'com.squareup.retrofit2:converter-gson:2.8.1'
|
||||
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.8.1'
|
||||
implementation 'io.reactivex.rxjava2:rxjava:2.2.16'
|
||||
implementation 'io.reactivex.rxjava2:rxjava:2.2.17'
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||
implementation "androidx.browser:browser:1.2.0"
|
||||
implementation 'com.google.android.material:material:1.1.0'
|
||||
implementation 'com.github.connyduck:sparkbutton:3.0.0'
|
||||
implementation 'com.github.connyduck:sparkbutton:4.0.0'
|
||||
|
||||
def room_version = "2.2.5"
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
@ -94,7 +94,7 @@ dependencies {
|
||||
// Excludes the support library because it's already included by Glide.
|
||||
transitive = false
|
||||
}
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
|
||||
kapt 'com.github.bumptech.glide:compiler:4.11.0'
|
||||
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
testImplementation "com.github.tomakehurst:wiremock-jre8:2.26.3"
|
||||
@ -110,6 +110,8 @@ dependencies {
|
||||
androidTestImplementation 'com.android.support.test.espresso:espresso-contrib:3.0.2'
|
||||
implementation 'androidx.paging:paging-runtime-ktx:2.1.2'
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0'
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
|
||||
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
androidTestImplementation 'androidx.test:rules:1.2.0'
|
||||
@ -117,7 +119,18 @@ dependencies {
|
||||
def fragment_version = '1.2.4'
|
||||
debugImplementation "androidx.fragment:fragment-testing:$fragment_version"
|
||||
|
||||
implementation 'com.karumi:dexter:6.1.2'
|
||||
// Use the most recent version of CameraX
|
||||
def camerax_version = '1.0.0-beta03'
|
||||
implementation "androidx.camera:camera-core:${camerax_version}"
|
||||
implementation "androidx.camera:camera-camera2:${camerax_version}"
|
||||
// CameraX Lifecycle library
|
||||
implementation "androidx.camera:camera-lifecycle:$camerax_version"
|
||||
|
||||
// CameraX View class
|
||||
implementation 'androidx.camera:camera-view:1.0.0-alpha10'
|
||||
|
||||
implementation 'com.karumi:dexter:6.1.0'
|
||||
|
||||
|
||||
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
|
||||
|
||||
|
116
app/src/androidTest/java/com/h/pixeldroid/CameraTest.kt
Normal file
116
app/src/androidTest/java/com/h/pixeldroid/CameraTest.kt
Normal file
@ -0,0 +1,116 @@
|
||||
package com.h.pixeldroid
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ContentValues
|
||||
import android.content.Intent
|
||||
import android.content.Intent.ACTION_CHOOSER
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.media.MediaScannerConnection
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.fragment.app.testing.launchFragmentInContainer
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import com.h.pixeldroid.fragments.CameraFragment
|
||||
import kotlinx.android.synthetic.main.camera_ui_container.*
|
||||
import org.hamcrest.CoreMatchers
|
||||
import org.hamcrest.Matcher
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import java.io.OutputStream
|
||||
|
||||
|
||||
class CameraTest {
|
||||
|
||||
@get:Rule
|
||||
val mRuntimePermissionRule: GrantPermissionRule = GrantPermissionRule.grant(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
|
||||
private fun File.writeBitmap(bitmap: Bitmap) {
|
||||
outputStream().use { out ->
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 85, out)
|
||||
out.flush()
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
fun before(){
|
||||
Intents.init()
|
||||
}
|
||||
@After
|
||||
fun after(){
|
||||
Intents.release()
|
||||
}
|
||||
@Test
|
||||
fun takePictureButton() {
|
||||
var scenario = launchFragmentInContainer<CameraFragment>()
|
||||
|
||||
scenario.onFragment { _ ->
|
||||
val image = Bitmap.createBitmap(500, 500, Bitmap.Config.ARGB_8888)
|
||||
image.eraseColor(Color.GREEN)
|
||||
val folder =
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
|
||||
if (!folder.exists()) {
|
||||
folder.mkdir()
|
||||
}
|
||||
val file = File.createTempFile("temp_img", ".png", folder)
|
||||
file.writeBitmap(image)
|
||||
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val mimeType = MimeTypeMap.getSingleton()
|
||||
.getMimeTypeFromExtension(file.extension)
|
||||
MediaScannerConnection.scanFile(
|
||||
context,
|
||||
arrayOf(file.absolutePath),
|
||||
arrayOf(mimeType)){_, _ ->
|
||||
}
|
||||
|
||||
}
|
||||
scenario = launchFragmentInContainer<CameraFragment>()
|
||||
|
||||
Thread.sleep(2000)
|
||||
scenario.onFragment { fragment ->
|
||||
fragment.camera_capture_button.performClick()
|
||||
}
|
||||
scenario.onFragment { fragment ->
|
||||
assert(fragment.isHidden)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun uploadButton() {
|
||||
val expectedIntent: Matcher<Intent> = CoreMatchers.allOf(
|
||||
IntentMatchers.hasAction(ACTION_CHOOSER)
|
||||
)
|
||||
|
||||
val scenario = launchFragmentInContainer<CameraFragment>()
|
||||
scenario.onFragment { fragment ->
|
||||
fragment.photo_view_button.performClick()
|
||||
}
|
||||
Thread.sleep(1000)
|
||||
|
||||
Intents.intended(expectedIntent)
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchButton() {
|
||||
val scenario = launchFragmentInContainer<CameraFragment>()
|
||||
scenario.onFragment { fragment ->
|
||||
fragment.camera_switch_button.performClick()
|
||||
}
|
||||
Thread.sleep(1000)
|
||||
scenario.onFragment { fragment ->
|
||||
assert(!fragment.isHidden)
|
||||
}
|
||||
}
|
||||
}
|
@ -54,7 +54,7 @@ class EditPhotoTest {
|
||||
|
||||
// Launch PhotoEditActivity
|
||||
val uri: Uri = Uri.parse("android.resource://com.h.pixeldroid/drawable/index")
|
||||
val intent = Intent(context, PhotoEditActivity::class.java).putExtra("uri", uri)
|
||||
val intent = Intent(context, PhotoEditActivity::class.java).putExtra("picture_uri", uri)
|
||||
|
||||
activityScenario = ActivityScenario.launch<PhotoEditActivity>(intent).onActivity{a -> activity = a}
|
||||
|
||||
@ -140,7 +140,7 @@ class EditPhotoTest {
|
||||
|
||||
@Test
|
||||
fun SaveButton() {
|
||||
Espresso.onView(withId(R.id.toolbar)).check(matches(isDisplayed()));
|
||||
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")))
|
||||
|
@ -18,6 +18,7 @@ import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import com.h.pixeldroid.testUtility.MockServer
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import kotlinx.android.synthetic.main.camera_ui_container.*
|
||||
import org.hamcrest.Matcher
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
@ -50,7 +51,7 @@ class PostCreationFragmentTest {
|
||||
fun uploadButtonLaunchesGalleryIntent() {
|
||||
val expectedIntent: Matcher<Intent> = hasAction(Intent.ACTION_CHOOSER)
|
||||
intending(expectedIntent)
|
||||
onView(withId(R.id.uploadPictureButton)).perform(click())
|
||||
onView(withId(R.id.photo_view_button)).perform(click())
|
||||
Thread.sleep(1000)
|
||||
intended(expectedIntent)
|
||||
}
|
||||
@ -73,16 +74,16 @@ class PostFragmentUITests {
|
||||
.targetContext.getSharedPreferences("com.h.pixeldroid.pref", Context.MODE_PRIVATE)
|
||||
preferences.edit().putString("accessToken", "azerty").apply()
|
||||
preferences.edit().putString("domain", baseUrl.toString()).apply()
|
||||
|
||||
ActivityScenario.launch(MainActivity::class.java).onActivity {
|
||||
a -> a.tabs.getTabAt(2)!!.select()
|
||||
}
|
||||
Thread.sleep(300)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun newPostUiTest() {
|
||||
onView(withId(R.id.uploadPictureButton)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.takePictureButton)).check(matches(isDisplayed()))
|
||||
ActivityScenario.launch(MainActivity::class.java).onActivity {
|
||||
a -> a.tabs.getTabAt(2)!!.select()
|
||||
}
|
||||
Thread.sleep(1500)
|
||||
onView(withId(R.id.photo_view_button)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.camera_capture_button)).check(matches(isDisplayed()))
|
||||
}
|
||||
}
|
@ -5,10 +5,9 @@
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera.any"
|
||||
|
@ -15,9 +15,9 @@ import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.h.pixeldroid.api.PixelfedAPI
|
||||
import com.h.pixeldroid.fragments.CameraFragment
|
||||
import com.h.pixeldroid.db.AppDatabase
|
||||
import com.h.pixeldroid.db.UserDatabaseEntity
|
||||
import com.h.pixeldroid.fragments.NewPostFragment
|
||||
import com.h.pixeldroid.fragments.SearchDiscoverFragment
|
||||
import com.h.pixeldroid.fragments.feeds.PostsFeedFragment
|
||||
import com.h.pixeldroid.fragments.feeds.NotificationsFragment
|
||||
@ -60,7 +60,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
||||
if (hasInternet(applicationContext)) PostsFeedFragment()
|
||||
else OfflineFeedFragment(),
|
||||
searchDiscoverFragment,
|
||||
NewPostFragment(),
|
||||
CameraFragment(),
|
||||
NotificationsFragment(),
|
||||
PublicTimelineFragment()
|
||||
)
|
||||
|
@ -97,7 +97,7 @@ class PhotoEditActivity : AppCompatActivity(), FilterListFragmentListener, EditI
|
||||
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar!!.setHomeButtonEnabled(true)
|
||||
|
||||
cropUri = intent.getParcelableExtra("uri")
|
||||
cropUri = intent.getParcelableExtra("picture_uri")
|
||||
|
||||
loadImage()
|
||||
val file = File.createTempFile("temp_compressed_img", ".png", cacheDir)
|
||||
|
407
app/src/main/java/com/h/pixeldroid/fragments/CameraFragment.kt
Normal file
407
app/src/main/java/com/h/pixeldroid/fragments/CameraFragment.kt
Normal file
@ -0,0 +1,407 @@
|
||||
package com.h.pixeldroid.fragments
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.hardware.display.DisplayManager
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.MediaStore
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageButton
|
||||
import androidx.camera.core.*
|
||||
import androidx.camera.core.ImageCapture.Metadata
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||
import androidx.camera.view.PreviewView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
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.R
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
// This is an arbitrary number we are using to keep track of the permission
|
||||
// request. Where an app has multiple context for requesting permission,
|
||||
// this can help differentiate the different contexts.
|
||||
private const val REQUEST_CODE_PERMISSIONS = 10
|
||||
private const val ANIMATION_FAST_MILLIS = 50L
|
||||
private const val ANIMATION_SLOW_MILLIS = 100L
|
||||
|
||||
/**
|
||||
* Camera fragment
|
||||
*/
|
||||
class CameraFragment : Fragment() {
|
||||
|
||||
private lateinit var container: ConstraintLayout
|
||||
private lateinit var viewFinder: PreviewView
|
||||
private lateinit var outputDirectory: File
|
||||
|
||||
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
|
||||
|
||||
private var displayId: Int = -1
|
||||
private var lensFacing: Int = CameraSelector.LENS_FACING_BACK
|
||||
private var preview: Preview? = null
|
||||
private var imageCapture: ImageCapture? = null
|
||||
private var camera: Camera? = null
|
||||
|
||||
private val displayManager by lazy {
|
||||
requireContext().getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||
}
|
||||
|
||||
/** Blocking camera operations are performed using this executor */
|
||||
private lateinit var cameraExecutor: ExecutorService
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
// Make sure that all permissions are still present on resume,
|
||||
// since they could have been removed while away.
|
||||
if (!allPermissionsGranted()) {
|
||||
ActivityCompat.requestPermissions(
|
||||
requireActivity(),
|
||||
REQUIRED_PERMISSIONS,
|
||||
REQUEST_CODE_PERMISSIONS
|
||||
)
|
||||
} else {
|
||||
//Bind the viewfinder here, since when leaving the fragment it gets unbound
|
||||
bindCameraUseCases()
|
||||
|
||||
// Build UI controls
|
||||
updateCameraUi()
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Check if all permission specified in the manifest have been granted
|
||||
*/
|
||||
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
|
||||
ContextCompat.checkSelfPermission(
|
||||
requireContext(), it) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
|
||||
// Shut down our background executor
|
||||
cameraExecutor.shutdown()
|
||||
|
||||
// Unregister the broadcast receivers and listeners
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? =
|
||||
inflater.inflate(R.layout.fragment_camera, container, false)
|
||||
|
||||
private fun setGalleryThumbnail(uri: String) {
|
||||
// Reference of the view that holds the gallery thumbnail
|
||||
val thumbnail = container.findViewById<ImageButton>(R.id.photo_view_button)
|
||||
|
||||
// Run the operations in the view's thread
|
||||
thumbnail.post {
|
||||
|
||||
// Remove thumbnail padding
|
||||
thumbnail.setPadding(10)
|
||||
|
||||
// Load thumbnail into circular button using Glide
|
||||
Glide.with(thumbnail)
|
||||
.load(uri)
|
||||
.apply(RequestOptions.circleCropTransform())
|
||||
.into(thumbnail)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
container = view as ConstraintLayout
|
||||
viewFinder = container.findViewById(R.id.view_finder)
|
||||
|
||||
// Initialize our background executor
|
||||
cameraExecutor = Executors.newSingleThreadExecutor()
|
||||
|
||||
// Every time the orientation of device changes, update rotation for use cases
|
||||
|
||||
// Determine the output directory
|
||||
outputDirectory = getGalleryDirectory(requireContext())
|
||||
|
||||
// Wait for the views to be properly laid out
|
||||
viewFinder.post {
|
||||
|
||||
// Keep track of the display in which this view is attached
|
||||
displayId = viewFinder.display?.displayId ?: -1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate camera controls and update the UI manually upon config changes to avoid removing
|
||||
* and re-adding the view finder from the view hierarchy; this provides a seamless rotation
|
||||
* transition on devices that support it.
|
||||
*
|
||||
* NOTE: The flag is supported starting in Android 8 but there still is a small flash on the
|
||||
* screen for devices that run Android 9 or below.
|
||||
*/
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
updateCameraUi()
|
||||
}
|
||||
|
||||
/** Declare and bind preview, capture and analysis use cases */
|
||||
private fun bindCameraUseCases() {
|
||||
|
||||
// Get screen metrics used to setup camera for full screen resolution
|
||||
val metrics = DisplayMetrics().also { viewFinder.display.getRealMetrics(it) }
|
||||
Log.d(TAG, "Screen metrics: ${metrics.widthPixels} x ${metrics.heightPixels}")
|
||||
|
||||
val screenAspectRatio = aspectRatio(metrics.widthPixels, metrics.heightPixels)
|
||||
Log.d(TAG, "Preview aspect ratio: $screenAspectRatio")
|
||||
|
||||
val rotation = viewFinder.display.rotation
|
||||
|
||||
// Bind the CameraProvider to the LifeCycleOwner
|
||||
val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
|
||||
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
|
||||
cameraProviderFuture.addListener(Runnable {
|
||||
|
||||
// CameraProvider
|
||||
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
|
||||
|
||||
// Preview
|
||||
preview = Preview.Builder()
|
||||
// We request aspect ratio but no resolution
|
||||
.setTargetAspectRatio(screenAspectRatio)
|
||||
// Set initial target rotation
|
||||
.setTargetRotation(rotation)
|
||||
.build()
|
||||
|
||||
// ImageCapture
|
||||
imageCapture = ImageCapture.Builder()
|
||||
.setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
|
||||
// We request aspect ratio but no resolution to match preview config, but letting
|
||||
// CameraX optimize for whatever specific resolution best fits our use cases
|
||||
.setTargetAspectRatio(screenAspectRatio)
|
||||
// Set initial target rotation, we will have to call this again if rotation changes
|
||||
// during the lifecycle of this use case
|
||||
.setTargetRotation(rotation)
|
||||
.build()
|
||||
|
||||
// Must unbind the use-cases before rebinding them
|
||||
cameraProvider.unbindAll()
|
||||
|
||||
try {
|
||||
// A variable number of use-cases can be passed here -
|
||||
// camera provides access to CameraControl & CameraInfo
|
||||
camera = cameraProvider.bindToLifecycle(
|
||||
this, cameraSelector, preview, imageCapture)
|
||||
|
||||
// Attach the viewfinder's surface provider to preview use case
|
||||
preview?.setSurfaceProvider(viewFinder.createSurfaceProvider(camera?.cameraInfo))
|
||||
} catch(exc: Exception) {
|
||||
Log.e(TAG, "Use case binding failed", exc)
|
||||
}
|
||||
|
||||
}, ContextCompat.getMainExecutor(requireContext()))
|
||||
}
|
||||
|
||||
/**
|
||||
* setTargetAspectRatio requires enum value of
|
||||
* [androidx.camera.core.AspectRatio]. Currently it has values of 4:3 & 16:9.
|
||||
*
|
||||
* Detecting the most suitable ratio for dimensions provided in @params by counting absolute
|
||||
* of preview ratio to one of the provided values.
|
||||
*
|
||||
* @param width - preview width
|
||||
* @param height - preview height
|
||||
* @return suitable aspect ratio
|
||||
*/
|
||||
private fun aspectRatio(width: Int, height: Int): Int {
|
||||
val previewRatio = max(width, height).toDouble() / min(width, height)
|
||||
if (abs(previewRatio - RATIO_4_3_VALUE) <= abs(previewRatio - RATIO_16_9_VALUE)) {
|
||||
return AspectRatio.RATIO_4_3
|
||||
}
|
||||
return AspectRatio.RATIO_16_9
|
||||
}
|
||||
|
||||
/** Method used to re-draw the camera UI controls, called every time configuration changes. */
|
||||
private fun updateCameraUi() {
|
||||
|
||||
// Remove previous UI if any
|
||||
container.findViewById<ConstraintLayout>(R.id.camera_ui_container)?.let {
|
||||
container.removeView(it)
|
||||
}
|
||||
|
||||
// Inflate a new view containing all UI for controlling the camera
|
||||
val controls = View.inflate(requireContext(), R.layout.camera_ui_container, container)
|
||||
|
||||
// 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,
|
||||
MediaStore.Images.ImageColumns.DATA,
|
||||
MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME,
|
||||
MediaStore.Images.ImageColumns.DATE_TAKEN,
|
||||
MediaStore.Images.ImageColumns.MIME_TYPE
|
||||
)
|
||||
val cursor = requireContext().contentResolver
|
||||
.query(
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null,
|
||||
null, MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC"
|
||||
)
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
val uri = Uri.parse(cursor.getString(1)).path ?: ""
|
||||
setGalleryThumbnail(uri)
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
setupImageCapture(controls)
|
||||
|
||||
setupFlipCameras(controls)
|
||||
|
||||
setupUploadImage(controls)
|
||||
}
|
||||
|
||||
private fun setupUploadImage(controls: View) {
|
||||
// Listener for button used to view the most recent photo
|
||||
controls.findViewById<ImageButton>(R.id.photo_view_button).setOnClickListener {
|
||||
Intent().apply {
|
||||
type = "image/*"
|
||||
action = Intent.ACTION_GET_CONTENT
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
putExtra(Intent.EXTRA_LOCAL_ONLY, true)
|
||||
startActivityForResult(
|
||||
Intent.createChooser(this, "Select a Picture"), PICK_IMAGE_REQUEST
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupFlipCameras(controls: View) {
|
||||
// Listener for button used to switch cameras
|
||||
controls.findViewById<ImageButton>(R.id.camera_switch_button).setOnClickListener {
|
||||
lensFacing = if (CameraSelector.LENS_FACING_FRONT == lensFacing) {
|
||||
CameraSelector.LENS_FACING_BACK
|
||||
} else {
|
||||
CameraSelector.LENS_FACING_FRONT
|
||||
}
|
||||
// Re-bind use cases to update selected camera, being careful about permissions.
|
||||
if (!allPermissionsGranted()) {
|
||||
ActivityCompat.requestPermissions(
|
||||
requireActivity(),
|
||||
REQUIRED_PERMISSIONS,
|
||||
REQUEST_CODE_PERMISSIONS
|
||||
)
|
||||
} else {
|
||||
bindCameraUseCases()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupImageCapture(controls: View) {
|
||||
// Listener for button used to capture photo
|
||||
controls.findViewById<ImageButton>(R.id.camera_capture_button).setOnClickListener {
|
||||
|
||||
// Get a stable reference of the modifiable image capture use case
|
||||
imageCapture?.let { imageCapture ->
|
||||
|
||||
// Create output file to hold the image
|
||||
val photoFile = File.createTempFile(
|
||||
"${System.currentTimeMillis()}.jpg", null, context?.cacheDir
|
||||
)
|
||||
|
||||
// Setup image capture metadata
|
||||
val metadata = Metadata().apply {
|
||||
|
||||
// Mirror image when using the front camera
|
||||
isReversedHorizontal = lensFacing == CameraSelector.LENS_FACING_FRONT
|
||||
}
|
||||
|
||||
// Create output options object which contains file + metadata
|
||||
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile)
|
||||
.setMetadata(metadata)
|
||||
.build()
|
||||
|
||||
// Setup image capture listener which is triggered after photo has been taken
|
||||
imageCapture.takePicture(
|
||||
outputOptions, cameraExecutor, object : ImageCapture.OnImageSavedCallback {
|
||||
override fun onError(exc: ImageCaptureException) {
|
||||
Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
|
||||
}
|
||||
|
||||
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
|
||||
val savedUri = output.savedUri ?: Uri.fromFile(photoFile)
|
||||
startPostCreation(savedUri)
|
||||
}
|
||||
})
|
||||
|
||||
// Display flash animation to indicate that photo was captured
|
||||
container.postDelayed({
|
||||
container.foreground = ColorDrawable(Color.WHITE)
|
||||
container.postDelayed(
|
||||
{ container.foreground = null }, ANIMATION_FAST_MILLIS
|
||||
)
|
||||
}, ANIMATION_SLOW_MILLIS)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (resultCode == Activity.RESULT_OK && data != null
|
||||
&& (requestCode == PICK_IMAGE_REQUEST || requestCode == CAPTURE_IMAGE_REQUEST)
|
||||
&& data.data != null) {
|
||||
|
||||
startPostCreation(data.data!!)
|
||||
}
|
||||
}
|
||||
private fun startPostCreation(uri: Uri) {
|
||||
startActivity(
|
||||
Intent(activity, PhotoEditActivity::class.java)
|
||||
.putExtra("picture_uri", uri)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val TAG = "CameraFragment"
|
||||
private const val RATIO_4_3_VALUE = 4.0 / 3.0
|
||||
private const val RATIO_16_9_VALUE = 16.0 / 9.0
|
||||
|
||||
/** Use external media if it is available, our app's file directory otherwise */
|
||||
private fun getGalleryDirectory(context: Context): File {
|
||||
val appContext = context.applicationContext
|
||||
val mediaDir = context.externalMediaDirs.firstOrNull()?.let {
|
||||
File(it, appContext.resources.getString(R.string.app_name)).apply { mkdirs() } }
|
||||
return if (mediaDir != null && mediaDir.exists())
|
||||
mediaDir else appContext.filesDir
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
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.PhotoEditActivity
|
||||
import com.h.pixeldroid.R
|
||||
|
||||
/**
|
||||
* This fragment is the entry point to create a post.
|
||||
* You can either upload an existing picture or take a new one.
|
||||
* once the URI of the picture to be posted is set, it will send
|
||||
* it to the post creation activity where you can modify it,
|
||||
* add a description and more.
|
||||
*/
|
||||
|
||||
class NewPostFragment : Fragment() {
|
||||
|
||||
private val PICK_IMAGE_REQUEST = 1
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(R.layout.fragment_new_post, container, false)
|
||||
|
||||
val uploadPictureButton: Button = view.findViewById(R.id.uploadPictureButton)
|
||||
uploadPictureButton.setOnClickListener{
|
||||
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, PhotoEditActivity::class.java)
|
||||
.putExtra("uri", data.data)
|
||||
)
|
||||
}
|
||||
|
||||
private fun uploadPicture() {
|
||||
Intent().apply {
|
||||
type = "image/*"
|
||||
action = Intent.ACTION_GET_CONTENT
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
putExtra(Intent.EXTRA_LOCAL_ONLY, true)
|
||||
startActivityForResult(
|
||||
Intent.createChooser(this, "Select a Picture"), PICK_IMAGE_REQUEST
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -285,6 +285,8 @@ data class Status(
|
||||
// Button is inactive
|
||||
undoReblogPost(holder, api, credential, this@Status)
|
||||
}
|
||||
//show animation or not?
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -309,7 +311,9 @@ data class Status(
|
||||
// Button is inactive
|
||||
unLikePostCall(holder, api, credential, this@Status)
|
||||
}
|
||||
}
|
||||
//show animation or not?
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
22
app/src/main/res/color/selector_button_text.xml
Normal file
22
app/src/main/res/color/selector_button_text.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2020 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true" android:color="#000000" />
|
||||
<item android:state_enabled="false" android:color="#000000" />
|
||||
<item android:state_focused="true" android:color="#000000" />
|
||||
<item android:color="#FFFFFF" />
|
||||
</selector>
|
21
app/src/main/res/color/selector_ic.xml
Normal file
21
app/src/main/res/color/selector_ic.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2020 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true" android:color="@color/icPressed" />
|
||||
<item android:state_focused="true" android:color="@color/icFocused" />
|
||||
<item android:color="@color/icActive" />
|
||||
</selector>
|
48
app/src/main/res/drawable/ic_outer_circle.xml
Normal file
48
app/src/main/res/drawable/ic_outer_circle.xml
Normal file
@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2020 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true">
|
||||
<shape android:shape="oval">
|
||||
<stroke
|
||||
android:width="@dimen/stroke_small"
|
||||
android:color="@color/icPressed" />
|
||||
<size
|
||||
android:width="@dimen/round_button_medium"
|
||||
android:height="@dimen/round_button_medium" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:state_focused="true">
|
||||
<shape android:shape="oval">
|
||||
<stroke
|
||||
android:width="@dimen/stroke_small"
|
||||
android:color="@color/icFocused" />
|
||||
<size
|
||||
android:width="@dimen/round_button_medium"
|
||||
android:height="@dimen/round_button_medium" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<stroke
|
||||
android:width="@dimen/stroke_small"
|
||||
android:color="@color/icActive" />
|
||||
<size
|
||||
android:width="@dimen/round_button_medium"
|
||||
android:height="@dimen/round_button_medium" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
26
app/src/main/res/drawable/ic_photo.xml
Normal file
26
app/src/main/res/drawable/ic_photo.xml
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2020 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:tint="@color/selector_ic"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
|
||||
</vector>
|
21
app/src/main/res/drawable/ic_shutter.xml
Normal file
21
app/src/main/res/drawable/ic_shutter.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2020 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true" android:drawable="@drawable/ic_shutter_pressed" />
|
||||
<item android:state_focused="true" android:drawable="@drawable/ic_shutter_focused" />
|
||||
<item android:drawable="@drawable/ic_shutter_normal" />
|
||||
</selector>
|
28
app/src/main/res/drawable/ic_shutter_focused.xml
Normal file
28
app/src/main/res/drawable/ic_shutter_focused.xml
Normal file
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2020 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="74"
|
||||
android:viewportHeight="74">
|
||||
<path android:fillColor="#FFFFFF" android:fillType="evenOdd"
|
||||
android:pathData="M73.1,37C73.1,17.0637 56.9373,0.9 37,0.9C17.0627,0.9 0.9,17.0637 0.9,37C0.9,56.9373 17.0627,73.1 37,73.1C56.9373,73.1 73.1,56.9373 73.1,37"
|
||||
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||
<path android:fillColor="#58A0C4" android:fillType="evenOdd"
|
||||
android:pathData="M67.4,37C67.4,53.7895 53.7895,67.4 37,67.4C20.2105,67.4 6.6,53.7895 6.6,37C6.6,20.2105 20.2105,6.6 37,6.6C53.7895,6.6 67.4,20.2105 67.4,37"
|
||||
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||
</vector>
|
28
app/src/main/res/drawable/ic_shutter_normal.xml
Normal file
28
app/src/main/res/drawable/ic_shutter_normal.xml
Normal file
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2020 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="74"
|
||||
android:viewportHeight="74">
|
||||
<path android:fillColor="#FFFFFF" android:fillType="evenOdd"
|
||||
android:pathData="M73.1,37C73.1,17.0637 56.9373,0.9 37,0.9C17.0627,0.9 0.9,17.0637 0.9,37C0.9,56.9373 17.0627,73.1 37,73.1C56.9373,73.1 73.1,56.9373 73.1,37"
|
||||
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||
<path android:fillColor="#CFD7DB" android:fillType="evenOdd"
|
||||
android:pathData="M67.4,37C67.4,53.7895 53.7895,67.4 37,67.4C20.2105,67.4 6.6,53.7895 6.6,37C6.6,20.2105 20.2105,6.6 37,6.6C53.7895,6.6 67.4,20.2105 67.4,37"
|
||||
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||
</vector>
|
28
app/src/main/res/drawable/ic_shutter_pressed.xml
Normal file
28
app/src/main/res/drawable/ic_shutter_pressed.xml
Normal file
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2020 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="74"
|
||||
android:viewportHeight="74">
|
||||
<path android:fillColor="#FFFFFF" android:fillType="evenOdd"
|
||||
android:pathData="M73.1,37C73.1,17.0637 56.9373,0.9 37,0.9C17.0627,0.9 0.9,17.0637 0.9,37C0.9,56.9373 17.0627,73.1 37,73.1C56.9373,73.1 73.1,56.9373 73.1,37"
|
||||
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||
<path android:fillColor="#58A0C4" android:fillType="evenOdd"
|
||||
android:pathData="M67.4,37C67.4,53.7895 53.7895,67.4 37,67.4C20.2105,67.4 6.6,53.7895 6.6,37C6.6,20.2105 20.2105,6.6 37,6.6C53.7895,6.6 67.4,20.2105 67.4,37"
|
||||
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||
</vector>
|
46
app/src/main/res/drawable/ic_switch.xml
Normal file
46
app/src/main/res/drawable/ic_switch.xml
Normal file
@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2020 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:tint="@color/selector_ic"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="46.0"
|
||||
android:viewportHeight="46.0"
|
||||
tools:ignore="VectorRaster">
|
||||
<path android:fillAlpha="0" android:fillColor="#D8D8D8"
|
||||
android:fillType="evenOdd" android:pathData="M-4,-4h54v54h-54z"
|
||||
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||
<path android:fillColor="#00000000" android:fillType="evenOdd"
|
||||
android:pathData="M23,23m-6.6,0a6.6,6.6 0,1 1,13.2 0a6.6,6.6 0,1 1,-13.2 0"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="2.8"/>
|
||||
<path android:fillColor="#FEFEFE" android:fillType="evenOdd"
|
||||
android:pathData="M42.923,34.5C38.9462,41.3747 31.5132,46 23,46C10.2975,46 0,35.7025 0,23L2.8,23C2.8,34.1562 11.8438,43.2 23,43.2C29.8831,43.2 35.9621,39.7574 39.6091,34.5L42.923,34.5ZM46,23L43.2,23C43.2,11.8438 34.1562,2.8 23,2.8C16.1169,2.8 10.0379,6.2426 6.3909,11.5L3.077,11.5C7.0538,4.6253 14.4868,0 23,0C35.7025,0 46,10.2975 46,23Z"
|
||||
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||
<path android:fillColor="#FEFEFE" android:fillType="evenOdd"
|
||||
android:pathData="M3,9.2h10v2.8h-10z"
|
||||
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||
<path android:fillColor="#FEFEFE" android:fillType="evenOdd"
|
||||
android:pathData="M33,34h10v2.8h-10z"
|
||||
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||
<path android:fillColor="#FEFEFE" android:fillType="evenOdd"
|
||||
android:pathData="M5.8,2l-0,10l-2.8,0l-0,-10z"
|
||||
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||
<path android:fillColor="#FEFEFE" android:fillType="evenOdd"
|
||||
android:pathData="M43,34l-0,10l-2.8,0l-0,-10z"
|
||||
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||
</vector>
|
66
app/src/main/res/layout-land/camera_ui_container.xml
Normal file
66
app/src/main/res/layout-land/camera_ui_container.xml
Normal file
@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2020 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/camera_ui_container"
|
||||
android:layoutDirection="ltr"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<!-- Camera control and gallery buttons -->
|
||||
<ImageButton
|
||||
android:id="@id/camera_switch_button"
|
||||
android:layout_width="@dimen/round_button_medium"
|
||||
android:layout_height="@dimen/round_button_medium"
|
||||
android:layout_marginEnd="@dimen/margin_xlarge"
|
||||
android:layout_marginBottom="@dimen/margin_small"
|
||||
android:padding="@dimen/spacing_small"
|
||||
android:scaleType="fitXY"
|
||||
android:background="@android:color/transparent"
|
||||
app:srcCompat="@drawable/ic_switch"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:contentDescription="@string/gallery_button_alt" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@id/camera_capture_button"
|
||||
android:layout_width="@dimen/round_button_large"
|
||||
android:layout_height="@dimen/round_button_large"
|
||||
android:layout_marginEnd="@dimen/shutter_button_margin"
|
||||
android:background="@drawable/ic_shutter"
|
||||
android:contentDescription="@string/capture_button_alt"
|
||||
android:scaleType="fitXY"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@id/photo_view_button"
|
||||
android:layout_width="@dimen/round_button_medium"
|
||||
android:layout_height="@dimen/round_button_medium"
|
||||
android:layout_marginEnd="@dimen/margin_xlarge"
|
||||
android:layout_marginTop="@dimen/margin_small"
|
||||
android:padding="@dimen/spacing_large"
|
||||
android:scaleType="fitXY"
|
||||
android:background="@drawable/ic_outer_circle"
|
||||
app:srcCompat="@drawable/ic_photo"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:contentDescription="@string/switch_camera_button_alt" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
66
app/src/main/res/layout/camera_ui_container.xml
Normal file
66
app/src/main/res/layout/camera_ui_container.xml
Normal file
@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2020 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/camera_ui_container"
|
||||
android:layoutDirection="ltr"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<!-- Camera control and gallery buttons -->
|
||||
<ImageButton
|
||||
android:id="@+id/camera_switch_button"
|
||||
android:layout_width="@dimen/round_button_medium"
|
||||
android:layout_height="@dimen/round_button_medium"
|
||||
android:layout_marginBottom="@dimen/margin_xlarge"
|
||||
android:layout_marginStart="@dimen/margin_small"
|
||||
android:padding="@dimen/spacing_small"
|
||||
android:scaleType="fitCenter"
|
||||
android:background="@android:color/transparent"
|
||||
app:srcCompat="@drawable/ic_switch"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:contentDescription="@string/switch_camera_button_alt" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/camera_capture_button"
|
||||
android:layout_width="@dimen/round_button_large"
|
||||
android:layout_height="@dimen/round_button_large"
|
||||
android:layout_marginBottom="@dimen/shutter_button_margin"
|
||||
android:scaleType="fitCenter"
|
||||
android:background="@drawable/ic_shutter"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:contentDescription="@string/capture_button_alt" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/photo_view_button"
|
||||
android:layout_width="@dimen/round_button_medium"
|
||||
android:layout_height="@dimen/round_button_medium"
|
||||
android:layout_marginBottom="@dimen/margin_xlarge"
|
||||
android:layout_marginEnd="@dimen/margin_small"
|
||||
android:padding="@dimen/spacing_large"
|
||||
android:scaleType="fitCenter"
|
||||
android:background="@drawable/ic_outer_circle"
|
||||
app:srcCompat="@drawable/ic_photo"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:contentDescription="@string/gallery_button_alt" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,39 +1,29 @@
|
||||
<?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:id="@+id/camera_fragment_main_linear_layout"
|
||||
<!--
|
||||
~ Copyright 2020 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/camera_container"
|
||||
android:background="@android:color/black"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_horizontal"
|
||||
tools:context=".fragments.CameraFragment"
|
||||
android:orientation="vertical">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<Button
|
||||
android:id="@+id/upload_picture_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:padding="15dp"
|
||||
android:layout_margin="15dp"
|
||||
android:text="@string/upload_a_picture"
|
||||
android:backgroundTint="@color/colorButtonBg"
|
||||
android:textColor="@color/colorButtonText"/>
|
||||
<Button
|
||||
android:id="@+id/edit_picture_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:padding="15dp"
|
||||
android:layout_margin="15dp"
|
||||
android:text="Edit a picture"
|
||||
android:backgroundTint="@color/colorButtonBg"
|
||||
android:textColor="@color/colorButtonText"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/uploaded_picture_view"
|
||||
<androidx.camera.view.PreviewView
|
||||
android:id="@+id/view_finder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="15dp"
|
||||
tools:srcCompat="@tools:sample/avatars" />
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
@ -1,47 +0,0 @@
|
||||
<?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:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/create_a_new_post"
|
||||
android:textSize="25sp"
|
||||
android:layout_marginBottom="50dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/uploadPictureButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="15dp"
|
||||
android:gravity="center"
|
||||
android:padding="15dp"
|
||||
android:text="@string/upload_a_picture"
|
||||
android:backgroundTint="@color/colorButtonBg"
|
||||
android:textColor="@color/colorButtonText"
|
||||
tools:ignore="PrivateResource" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/or"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/takePictureButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="15dp"
|
||||
android:gravity="center"
|
||||
android:padding="15dp"
|
||||
android:text="@string/take_a_picture"
|
||||
android:backgroundTint="@color/colorButtonBg"
|
||||
android:textColor="@color/colorButtonText"
|
||||
tools:ignore="PrivateResource" />
|
||||
|
||||
</LinearLayout>
|
||||
|
@ -4,9 +4,11 @@
|
||||
<color name="colorPrimaryTab">#FFFFFF</color>
|
||||
<color name="colorPrimaryDark">#3700B3</color>
|
||||
<color name="colorAccent">#03DAC5</color>
|
||||
<color name="icActive">#FFFFFFFF</color>
|
||||
<color name="icFocused">#DDFFFFFF</color>
|
||||
<color name="icPressed">#AAFFFFFF</color>
|
||||
<color name="colorButtonBg">#6200EE</color>
|
||||
<color name="colorButtonText">#FFFFFF</color>
|
||||
|
||||
<color name="filterLabelNormal">#8A8889</color>
|
||||
<color name="filterLabelSelected">#221F20</color>
|
||||
<color name="colorOptionMenu">#FF3990</color>
|
||||
|
@ -5,4 +5,27 @@
|
||||
<dimen name="nav_header_vertical_spacing">8dp</dimen>
|
||||
<dimen name="nav_header_height">176dp</dimen>
|
||||
|
||||
<dimen name="margin_xsmall">16dp</dimen>
|
||||
<dimen name="margin_small">32dp</dimen>
|
||||
<dimen name="margin_medium">48dp</dimen>
|
||||
<dimen name="margin_large">64dp</dimen>
|
||||
<dimen name="margin_xlarge">92dp</dimen>
|
||||
|
||||
<dimen name="spacing_small">4dp</dimen>
|
||||
<dimen name="spacing_medium">8dp</dimen>
|
||||
<dimen name="spacing_large">16dp</dimen>
|
||||
|
||||
<dimen name="stroke_small">4dp</dimen>
|
||||
<dimen name="stroke_medium">8dp</dimen>
|
||||
<dimen name="stroke_large">12dp</dimen>
|
||||
|
||||
<dimen name="round_button_small">32dp</dimen>
|
||||
<dimen name="round_button_medium">64dp</dimen>
|
||||
<dimen name="round_button_large">92dp</dimen>
|
||||
|
||||
<dimen name="rect_button_margin">12dp</dimen>
|
||||
<dimen name="rect_button_width">80dp</dimen>
|
||||
<dimen name="rect_button_height">24dp</dimen>
|
||||
<dimen name="shutter_button_margin">80dp</dimen>
|
||||
|
||||
</resources>
|
||||
|
@ -40,6 +40,11 @@
|
||||
<string name="image_download_failed">Download has been failed, please try again</string>
|
||||
<string name="image_download_downloading">Downloading…</string>
|
||||
<string name="image_download_success">Image downloaded successfully</string>
|
||||
<string name="capture_button_alt">Capture</string>
|
||||
<string name="switch_camera_button_alt">Switch camera</string>
|
||||
<string name="gallery_button_alt">Gallery</string>
|
||||
<string name="capture_mode_camera">Camera</string>
|
||||
<string name="delete_title">Confirm</string>
|
||||
<string name="share_picture">Share picture…</string>
|
||||
<string name="NoCommentsToShow">No comments on this post…</string>
|
||||
<string name="CommentDisplay"> to show…</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user