Skip to content

Commit 0598f30

Browse files
committed
WIP: upload profile picture from camera
Signed-off-by: Álvaro Brey <[email protected]>
1 parent 8f2a199 commit 0598f30

8 files changed

Lines changed: 204 additions & 22 deletions

File tree

app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import com.nextcloud.talk.dagger.modules.ContextModule
5757
import com.nextcloud.talk.dagger.modules.DatabaseModule
5858
import com.nextcloud.talk.dagger.modules.RepositoryModule
5959
import com.nextcloud.talk.dagger.modules.RestModule
60+
import com.nextcloud.talk.dagger.modules.UtilsModule
6061
import com.nextcloud.talk.dagger.modules.ViewModelModule
6162
import com.nextcloud.talk.jobs.AccountRemovalWorker
6263
import com.nextcloud.talk.jobs.CapabilitiesWorker
@@ -99,7 +100,8 @@ import javax.inject.Singleton
99100
UserModule::class,
100101
ArbitraryStorageModule::class,
101102
ViewModelModule::class,
102-
RepositoryModule::class
103+
RepositoryModule::class,
104+
UtilsModule::class
103105
]
104106
)
105107
@Singleton

app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID
169169
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
170170
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
171171
import com.nextcloud.talk.utils.database.user.UserUtils
172+
import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
172173
import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder
173174
import com.nextcloud.talk.utils.text.Spans
174175
import com.nextcloud.talk.webrtc.MagicWebSocketInstance
@@ -231,6 +232,9 @@ class ChatController(args: Bundle) :
231232
@JvmField
232233
var eventBus: EventBus? = null
233234

235+
@Inject
236+
lateinit var permissionUtil: PlatformPermissionUtil
237+
234238
val disposableList = ArrayList<Disposable>()
235239

236240
var roomToken: String? = null
@@ -3128,7 +3132,7 @@ class ChatController(args: Bundle) :
31283132
}
31293133

31303134
fun sendPictureFromCamIntent() {
3131-
if (!isCameraPermissionGranted()) {
3135+
if (!permissionUtil.isCameraPermissionGranted()) {
31323136
requestCameraPermissions()
31333137
} else {
31343138
startActivityForResult(TakePhotoActivity.createIntent(context!!), REQUEST_CODE_PICK_CAMERA)

app/src/main/java/com/nextcloud/talk/controllers/ProfileController.kt

Lines changed: 79 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
package com.nextcloud.talk.controllers
2323

2424
import android.app.Activity
25+
import android.content.ActivityNotFoundException
2526
import android.content.Intent
27+
import android.content.pm.PackageManager
2628
import android.content.res.ColorStateList
2729
import android.graphics.Bitmap
2830
import android.graphics.BitmapFactory
@@ -52,6 +54,7 @@ import com.github.dhaval2404.imagepicker.ImagePicker.Companion.getError
5254
import com.github.dhaval2404.imagepicker.ImagePicker.Companion.getFile
5355
import com.github.dhaval2404.imagepicker.ImagePicker.Companion.with
5456
import com.nextcloud.talk.R
57+
import com.nextcloud.talk.activities.TakePhotoActivity
5558
import com.nextcloud.talk.api.NcApi
5659
import com.nextcloud.talk.application.NextcloudTalkApplication
5760
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
@@ -76,6 +79,7 @@ import com.nextcloud.talk.utils.Mimetype.IMAGE_PREFIX
7679
import com.nextcloud.talk.utils.Mimetype.IMAGE_PREFIX_GENERIC
7780
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_MIME_TYPE_FILTER
7881
import com.nextcloud.talk.utils.database.user.UserUtils
82+
import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
7983
import io.reactivex.Observer
8084
import io.reactivex.android.schedulers.AndroidSchedulers
8185
import io.reactivex.disposables.Disposable
@@ -95,6 +99,7 @@ import java.util.Locale
9599
import javax.inject.Inject
96100

97101
@AutoInjector(NextcloudTalkApplication::class)
102+
@Suppress("Detekt.TooManyFunctions")
98103
class ProfileController : NewBaseController(R.layout.controller_profile) {
99104
private val binding: ControllerProfileBinding by viewBinding(ControllerProfileBinding::bind)
100105

@@ -104,6 +109,9 @@ class ProfileController : NewBaseController(R.layout.controller_profile) {
104109
@Inject
105110
lateinit var userUtils: UserUtils
106111

112+
@Inject
113+
lateinit var permissionUtil: PlatformPermissionUtil
114+
107115
private var currentUser: UserEntity? = null
108116
private var edit = false
109117
private var adapter: UserInfoAdapter? = null
@@ -197,6 +205,7 @@ class ProfileController : NewBaseController(R.layout.controller_profile) {
197205
val credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token)
198206
binding.avatarUpload.setOnClickListener { sendSelectLocalFileIntent() }
199207
binding.avatarChoose.setOnClickListener { showBrowserScreen() }
208+
binding.avatarCamera.setOnClickListener { checkPermissionAndTakePicture() }
200209
binding.avatarDelete.setOnClickListener {
201210
ncApi.deleteAvatar(
202211
credentials,
@@ -493,7 +502,23 @@ class ProfileController : NewBaseController(R.layout.controller_profile) {
493502
startActivityForResult(avatarIntent, REQUEST_CODE_SELECT_REMOTE_FILES)
494503
}
495504

496-
fun handleAvatar(remotePath: String?) {
505+
private fun checkPermissionAndTakePicture() {
506+
if (permissionUtil.isCameraPermissionGranted()) {
507+
takePictureForAvatar()
508+
} else {
509+
requestPermissions(arrayOf(android.Manifest.permission.CAMERA), REQUEST_PERMISSION_CAMERA)
510+
}
511+
}
512+
513+
private fun takePictureForAvatar() {
514+
try {
515+
startActivityForResult(TakePhotoActivity.createIntent(context!!), REQUEST_CODE_TAKE_PICTURE)
516+
} catch (e: ActivityNotFoundException) {
517+
// TODO
518+
}
519+
}
520+
521+
private fun handleAvatar(remotePath: String?) {
497522
val uri = currentUser!!.baseUrl + "/index.php/apps/files/api/v1/thumbnail/512/512/" +
498523
Uri.encode(remotePath, "/")
499524
val downloadCall = ncApi.downloadResizedImage(
@@ -511,30 +536,54 @@ class ProfileController : NewBaseController(R.layout.controller_profile) {
511536
})
512537
}
513538

539+
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
540+
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
541+
if (requestCode == REQUEST_PERMISSION_CAMERA) {
542+
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
543+
takePictureForAvatar()
544+
} else {
545+
Toast
546+
.makeText(context, context?.getString(R.string.take_photo_permission), Toast.LENGTH_LONG)
547+
.show()
548+
}
549+
}
550+
}
551+
514552
// only possible with API26
515553
private fun saveBitmapAndPassToImagePicker(bitmap: Bitmap) {
516-
var file: File? = null
554+
val file: File = saveBitmapToTempFile(bitmap) ?: return
555+
openImageWithPicker(file)
556+
}
557+
558+
private fun saveBitmapToTempFile(bitmap: Bitmap): File? {
517559
try {
518-
FileUtils.removeTempCacheFile(
519-
this.context!!,
520-
AVATAR_PATH
521-
)
522-
file = FileUtils.getTempCacheFile(
523-
this.context!!,
524-
AVATAR_PATH
525-
)
560+
val file = createTempFileForAvatar()
526561
try {
527-
FileOutputStream(file).use { out -> bitmap.compress(Bitmap.CompressFormat.PNG, FULL_QUALITY, out) }
562+
FileOutputStream(file).use { out ->
563+
bitmap.compress(Bitmap.CompressFormat.PNG, FULL_QUALITY, out)
564+
}
565+
return file
528566
} catch (e: IOException) {
529567
Log.e(TAG, "Error compressing bitmap", e)
530568
}
531569
} catch (e: IOException) {
532570
Log.e(TAG, "Error creating temporary avatar image", e)
533571
}
534-
if (file == null) {
535-
// TODO exception
536-
return
537-
}
572+
return null
573+
}
574+
575+
private fun createTempFileForAvatar(): File? {
576+
FileUtils.removeTempCacheFile(
577+
this.context!!,
578+
AVATAR_PATH
579+
)
580+
return FileUtils.getTempCacheFile(
581+
context!!,
582+
AVATAR_PATH
583+
)
584+
}
585+
586+
private fun openImageWithPicker(file: File) {
538587
val intent = with(activity!!)
539588
.fileOnly()
540589
.crop()
@@ -546,20 +595,24 @@ class ProfileController : NewBaseController(R.layout.controller_profile) {
546595
startActivityForResult(intent, REQUEST_CODE_IMAGE_PICKER)
547596
}
548597

549-
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
598+
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
550599
if (resultCode == Activity.RESULT_OK) {
551600
if (requestCode == REQUEST_CODE_IMAGE_PICKER) {
552-
uploadAvatar(getFile(intent))
601+
uploadAvatar(getFile(data))
553602
} else if (requestCode == REQUEST_CODE_SELECT_REMOTE_FILES) {
554-
val pathList = intent?.getStringArrayListExtra(RemoteFileBrowserActivity.EXTRA_SELECTED_PATHS)
603+
val pathList = data?.getStringArrayListExtra(RemoteFileBrowserActivity.EXTRA_SELECTED_PATHS)
555604
if (pathList?.size!! >= 1) {
556605
handleAvatar(pathList[0])
557606
}
607+
} else if (requestCode == REQUEST_CODE_TAKE_PICTURE) {
608+
data?.data?.path?.let {
609+
openImageWithPicker(File(it))
610+
}
558611
} else {
559612
Log.w(TAG, "Unknown intent request code")
560613
}
561614
} else if (resultCode == ImagePicker.RESULT_ERROR) {
562-
Toast.makeText(activity, getError(intent), Toast.LENGTH_SHORT).show()
615+
Toast.makeText(activity, getError(data), Toast.LENGTH_SHORT).show()
563616
} else {
564617
Log.i(TAG, "Task Cancelled")
565618
}
@@ -595,7 +648,11 @@ class ProfileController : NewBaseController(R.layout.controller_profile) {
595648
}
596649

597650
override fun onError(e: Throwable) {
598-
Toast.makeText(applicationContext, "Error", Toast.LENGTH_LONG).show()
651+
Toast.makeText(
652+
applicationContext, context!!.getString(R.string.default_error_msg),
653+
Toast
654+
.LENGTH_LONG
655+
).show()
599656
Log.e(TAG, "Error uploading avatar", e)
600657
}
601658

@@ -823,6 +880,8 @@ class ProfileController : NewBaseController(R.layout.controller_profile) {
823880
private const val DEFAULT_RETRIES: Long = 3
824881
private const val MAX_SIZE: Int = 1024
825882
private const val REQUEST_CODE_IMAGE_PICKER: Int = 1
883+
private const val REQUEST_CODE_TAKE_PICTURE: Int = 2
884+
private const val REQUEST_PERMISSION_CAMERA: Int = 1
826885
private const val FULL_QUALITY: Int = 100
827886
private const val HIGH_EMPHASIS_ALPHA: Float = 0.87f
828887
private const val MEDIUM_EMPHASIS_ALPHA: Float = 0.6f
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Nextcloud Talk application
3+
*
4+
* @author Álvaro Brey
5+
* Copyright (C) 2022 Álvaro Brey
6+
* Copyright (C) 2022 Nextcloud GmbH
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU General Public License as published by
10+
* the Free Software Foundation, either version 3 of the License, or
11+
* (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU General Public License
19+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
20+
*/
21+
22+
package com.nextcloud.talk.dagger.modules
23+
24+
import android.content.Context
25+
import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
26+
import com.nextcloud.talk.utils.permissions.PlatformPermissionUtilImpl
27+
import dagger.Module
28+
import dagger.Provides
29+
import dagger.Reusable
30+
31+
@Module(includes = [ContextModule::class])
32+
class UtilsModule {
33+
@Provides
34+
@Reusable
35+
fun providePermissionUtil(context: Context): PlatformPermissionUtil {
36+
return PlatformPermissionUtilImpl(context)
37+
}
38+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Nextcloud Talk application
3+
*
4+
* @author Álvaro Brey
5+
* Copyright (C) 2022 Álvaro Brey
6+
* Copyright (C) 2022 Nextcloud GmbH
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU General Public License as published by
10+
* the Free Software Foundation, either version 3 of the License, or
11+
* (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU General Public License
19+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
20+
*/
21+
22+
package com.nextcloud.talk.utils.permissions
23+
24+
interface PlatformPermissionUtil {
25+
fun isCameraPermissionGranted(): Boolean
26+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Nextcloud Talk application
3+
*
4+
* @author Álvaro Brey
5+
* Copyright (C) 2022 Álvaro Brey
6+
* Copyright (C) 2022 Nextcloud GmbH
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU General Public License as published by
10+
* the Free Software Foundation, either version 3 of the License, or
11+
* (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU General Public License
19+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
20+
*/
21+
22+
package com.nextcloud.talk.utils.permissions
23+
24+
import android.Manifest
25+
import android.content.Context
26+
import android.os.Build
27+
import androidx.core.content.PermissionChecker
28+
29+
class PlatformPermissionUtilImpl(private val context: Context) : PlatformPermissionUtil {
30+
31+
override fun isCameraPermissionGranted(): Boolean {
32+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
33+
return PermissionChecker.checkSelfPermission(
34+
context,
35+
Manifest.permission.CAMERA
36+
) == PermissionChecker.PERMISSION_GRANTED
37+
} else {
38+
true
39+
}
40+
}
41+
}

app/src/main/res/layout/controller_profile.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,17 @@
9898
android:src="@drawable/ic_mimetype_folder"
9999
app:tint="@color/colorPrimary" />
100100

101+
<ImageButton
102+
android:id="@+id/avatar_camera"
103+
android:layout_width="@dimen/min_size_clickable_area"
104+
android:layout_height="@dimen/min_size_clickable_area"
105+
android:layout_marginLeft="@dimen/standard_half_margin"
106+
android:layout_marginRight="@dimen/standard_half_margin"
107+
android:background="@drawable/round_corner"
108+
android:contentDescription="@string/set_avatar_from_camera"
109+
android:src="@drawable/ic_baseline_photo_camera_24"
110+
app:tint="@color/colorPrimary" />
111+
101112
<ImageButton
102113
android:id="@+id/avatar_delete"
103114
android:layout_width="@dimen/min_size_clickable_area"

app/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,5 +532,6 @@
532532
<string name="reactions_tab_all">All</string>
533533
<string name="send_without_notification">Send without notification</string>
534534
<string name="call_without_notification">Call without notification</string>
535+
<string name="set_avatar_from_camera">Set avatar from camera</string>
535536

536537
</resources>

0 commit comments

Comments
 (0)