Skip to content

Commit

Permalink
Allow to take a profile picture using the camera.
Browse files Browse the repository at this point in the history
camera working with the icon

camera feature integrated

addding comments to all my files

comments added
  • Loading branch information
srsingh04 authored and alejandrocalles committed Jun 2, 2024
1 parent e0c0db1 commit c25828b
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 34 deletions.
3 changes: 2 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera.any"/>

<application
android:name=".EchoApplication"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,19 @@ import kotlinx.coroutines.flow.map

private val Context.dataStore by preferencesDataStore(name = "settings")

// Class that manages the theme preference.
class ThemePreferenceManager @Inject constructor(context: Context) {
private val dataStore = context.dataStore
private val THEME_KEY = stringPreferencesKey("theme")

// Flow that emits the current theme
val theme: Flow<AppTheme> =
dataStore.data.map { preferences ->
val themeString = preferences[THEME_KEY] ?: getSystemDefaultTheme(context).name
AppTheme.valueOf(themeString)
}

// Get the system default theme
fun getSystemDefaultTheme(context: Context): AppTheme {
return if (
context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
Expand All @@ -32,7 +35,7 @@ class ThemePreferenceManager @Inject constructor(context: Context) {
AppTheme.MODE_DAY
}
}

// Set the theme
suspend fun setTheme(theme: AppTheme) {
dataStore.edit { preferences -> preferences[THEME_KEY] = theme.name }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package com.github.swent.echo.compose.authentication

import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.provider.MediaStore
import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.Companion.isPhotoPickerAvailable
Expand Down Expand Up @@ -264,14 +270,14 @@ fun ProfileCreationUI(
) {
Row {
Column {
// Profile picture
ProfilePictureEdit(picture, onPictureChange)

// First name and last name fields
OutlinedTextField(
value = firstName,
onValueChange = { onFirstNameChange(it) },
modifier =
modifier
// .fillMaxWidth()
.testTag("FirstName"),
modifier = modifier.fillMaxWidth().testTag("FirstName"),
label = {
Text(
text = stringResource(id = R.string.profile_creation_first_name)
Expand All @@ -283,13 +289,12 @@ fun ProfileCreationUI(

Spacer(modifier = modifier.height(spacerHeight))

// Last name field

OutlinedTextField(
value = lastName,
onValueChange = { onLastNameChange(it) },
modifier =
modifier
// .fillMaxWidth()
.testTag("LastName"),
modifier = modifier.fillMaxWidth().testTag("LastName"),
label = {
Text(
text = stringResource(id = R.string.profile_creation_last_name)
Expand All @@ -299,25 +304,26 @@ fun ProfileCreationUI(
isError = lastName.isBlank()
)
}
ProfilePictureEdit(picture, onPictureChange)
}
Spacer(modifier = modifier.height(spacerHeight))

// Section and semester dropdown menus

DropDownListFunctionWrapper(
sectionList,
R.string.section,
selectedSec ?: "",
onSecChange
)

Spacer(modifier = modifier.height(spacerHeight))

DropDownListFunctionWrapper(
semList,
R.string.select_semester,
selectedSem ?: "",
onSemChange
)

Spacer(modifier = modifier.height(spacerHeight.times(2)))

// Tags
Expand All @@ -335,6 +341,7 @@ fun ProfileCreationUI(
}

// Add tag button

SmallFloatingActionButton(
onClick = { onAdd() },
containerColor = MaterialTheme.colorScheme.secondaryContainer,
Expand All @@ -344,6 +351,7 @@ fun ProfileCreationUI(
Icon(Icons.Default.Add, "Add tags")
}
}

Spacer(modifier = modifier.weight(1f))
val errorLN = stringResource(R.string.profile_creation_empty_LN)
val errorFN = stringResource(R.string.profile_creation_empty_FN)
Expand Down Expand Up @@ -401,7 +409,7 @@ fun DropDownListFunctionWrapper(
var selectedFieldSize by remember { mutableStateOf(Size.Zero) }
val icon = if (showDropdown) Icons.Filled.KeyboardArrowUp else Icons.Filled.KeyboardArrowDown
Column {
Box() {
Box {
OutlinedTextField(
value = selectedField,
onValueChange = {},
Expand Down Expand Up @@ -477,6 +485,7 @@ fun InputChipFun(
@Composable
fun ProfilePictureEdit(picture: Bitmap?, onPictureChange: (newPicture: Bitmap?) -> Unit) {
var showPictureDialog by remember { mutableStateOf(false) }
var showCamera by remember { mutableStateOf(false) }
val localContext = LocalContext.current
if (!isPhotoPickerAvailable(localContext)) {
Log.e("CreateProfile", "Photo picker not available")
Expand All @@ -496,10 +505,27 @@ fun ProfilePictureEdit(picture: Bitmap?, onPictureChange: (newPicture: Bitmap?)
}
}
}

val pictureDisplaySize = 100.dp
val pictureStartPadding = 5.dp
val pictureAlpha = 0.5f
val deleteButtonOffset = 10.dp

val requestPermissionLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) {
isGranted: Boolean ->
if (isGranted) {
showCamera = true
}
}
val cameraLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result
->
if (result.resultCode == Activity.RESULT_OK) {
val imageBitmap = result.data?.extras?.get("data") as Bitmap?
imageBitmap?.let { handleCapturedImage(it, onPictureChange) }
}
}

Column(modifier = Modifier.padding(start = pictureStartPadding)) {
Box {
Image(
Expand Down Expand Up @@ -529,17 +555,29 @@ fun ProfilePictureEdit(picture: Bitmap?, onPictureChange: (newPicture: Bitmap?)
modifier = Modifier.align(Alignment.Center)
)
}
IconButton(
modifier =
Modifier.align(Alignment.End)
.offset(x = deleteButtonOffset, y = -deleteButtonOffset)
.testTag("profile-picture-delete"),
onClick = { onPictureChange(null) }
) {
Icon(
imageVector = Icons.Outlined.Delete,
contentDescription = stringResource(R.string.profile_creation_delete_picture),
)
Row {
if (showCamera) {
dispatchTakePictureIntent(cameraLauncher)
showCamera = false
}
IconButton(
modifier = Modifier.testTag("profile-picture-camera"),
onClick = { requestPermissionLauncher.launch(Manifest.permission.CAMERA) },
) {
Icon(
painter = painterResource(R.drawable.camera_foreground),
contentDescription = "Take a picture",
)
}
IconButton(
modifier = Modifier.testTag("profile-picture-delete"),
onClick = { onPictureChange(null) },
) {
Icon(
imageVector = Icons.Outlined.Delete,
contentDescription = stringResource(R.string.profile_creation_delete_picture),
)
}
}
}

Expand Down Expand Up @@ -654,3 +692,28 @@ fun PictureTransformer(
}
}
}

/**
* This function is used to update the profile picture with the captured image by the camera
*
* @param bitmap: the captured image bitmap
* @param setPicture: the callback to update the profile picture
*/
private fun handleCapturedImage(bitmap: Bitmap, setPicture: (Bitmap) -> Unit) {
// Process the captured image bitmap here
setPicture(bitmap)
}
/**
* This function is used to dispatch the camera intent to take a picture
*
* @param cameraLauncher: the launcher to start the camera activity
*/
private fun dispatchTakePictureIntent(cameraLauncher: ActivityResultLauncher<Intent>) {
val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
try {
cameraLauncher.launch(takePictureIntent)
} catch (_: ActivityNotFoundException) {
// display error state to the user
Log.e("CreateProfile", "Error!")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import androidx.compose.ui.unit.sp
import com.github.swent.echo.R
import kotlinx.coroutines.delay

// Connectivity status
@Composable
fun ConnectivityStatus(isConnected: Boolean) {
var visibility by remember { mutableStateOf(false) }
Expand All @@ -50,13 +51,15 @@ fun ConnectivityStatus(isConnected: Boolean) {
}
}

// Connectivity status box
@Composable
fun ConnectivityStatusBox(isConnected: Boolean, modifier: Modifier = Modifier) {
val backgroundColor by animateColorAsState(if (!isConnected) Color.DarkGray else Color.Green)
val message = if (isConnected) R.string.Online_mode else R.string.Offline_mode
SmallTopAppBarFunc(text = stringResource(id = message), color = backgroundColor)
}

// TopAppBar (Online/Offline Banner) for the connectivity status
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SmallTopAppBarFunc(text: String, color: Color) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ constructor(
themePreferenceManager.theme.collect { theme -> _themeUserSetting.value = theme }
}
}

// Toggle the theme by clicking on the button
fun toggleTheme() {
viewModelScope.launch {
val newTheme =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.github.swent.echo.viewmodels.authentication

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.swent.echo.authentication.AuthenticationService
Expand Down Expand Up @@ -129,15 +128,11 @@ constructor(

val userId = authenticationService.getCurrentUserID()
if (userId == null) {
Log.d("nullUserId", "User ID is null")
_errorMessage.value = "Profile creation error: Not logged in"
} else {
// TODO: show loading animation while saving the profile
viewModelScope.launch {
_state.value = CreateProfileState.SAVING
println("User profile name: ${_firstName.value}\n")
println("User profile section: ${_selectedSection.value}\n")
println("User profile semester: ${_selectedSemester.value}\n")
repository.setUserProfile(
UserProfile(
userId,
Expand Down Expand Up @@ -166,10 +161,12 @@ constructor(
_tagList.value += tag
}

// Remove tag button
fun removeTag(tag: Tag) {
_tagList.value -= tag
}

// set profile picture to update it
fun setPicture(picture: Bitmap?) {
_picture.value = picture
}
Expand Down
Binary file added app/src/main/light_bulb-playstore.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions app/src/main/res/drawable/camera_foreground.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#000000">
<group android:scaleX="0.58"
android:scaleY="0.58"
android:translateX="5.04"
android:translateY="5.04">
<path
android:fillColor="@android:color/white"
android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
<path
android:fillColor="@android:color/white"
android:pathData="M9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2L9,2zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/>
</group>
</vector>
Loading

0 comments on commit c25828b

Please sign in to comment.