diff --git a/app/build.gradle b/app/build.gradle index dacfffbdac50de8adb948c4e6689af532fa8a3f8..87ab9d3278a977ce07c7dd5a2fcd0683a1514f09 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,7 +1,7 @@ plugins { id 'com.android.application' id 'kotlin-android' - id 'org.jetbrains.kotlin.plugin.serialization' version '1.3.72' // vérifier que votre version de kotlin est la même + id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlin_version" // vérifier que votre version de kotlin est la même } android { @@ -70,4 +70,7 @@ dependencies { implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" + + // Coil + implementation("io.coil-kt:coil:1.1.0") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9e0e412e2919ca1228258d45c6b8fda38472dcc9..8cd83cd6b8395ebe55c20df5039a45cf7b1ac0f5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,12 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.clemhaowen.dm_td2"> + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.CAMERA" /> + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + + <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" @@ -9,7 +14,8 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.DM_td2"> - <activity android:name=".task.TaskActivity"></activity> + <activity android:name=".userInfo.UserInfoActivity"></activity> + <activity android:name=".task.TaskActivity" /> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> diff --git a/app/src/main/java/com/clemhaowen/dm_td2/network/Api.kt b/app/src/main/java/com/clemhaowen/dm_td2/network/Api.kt index cfb6b24553d3a9abeafcb1c752e53e493115bfce..9164760442a7b0cb7315d378d89b6efbe1103fa4 100644 --- a/app/src/main/java/com/clemhaowen/dm_td2/network/Api.kt +++ b/app/src/main/java/com/clemhaowen/dm_td2/network/Api.kt @@ -44,4 +44,8 @@ object Api { val userService: UserService by lazy { retrofit.create(UserService::class.java) } + + val tasksWebService: TasksWebService by lazy { + retrofit.create(TasksWebService::class.java) + } } \ No newline at end of file diff --git a/app/src/main/java/com/clemhaowen/dm_td2/network/TasksRepository.kt b/app/src/main/java/com/clemhaowen/dm_td2/network/TasksRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..e07443025e676b850163ce70d98d9fd07afdb08f --- /dev/null +++ b/app/src/main/java/com/clemhaowen/dm_td2/network/TasksRepository.kt @@ -0,0 +1,61 @@ +package com.clemhaowen.dm_td2.network + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.clemhaowen.dm_td2.tasklist.Task +import retrofit2.http.Body +import retrofit2.http.Path + +class TasksRepository { + private val tasksWebService = Api.tasksWebService + + // Ces deux variables encapsulent la même donnée: + // [_taskList] est modifiable mais privée donc inaccessible à l'extérieur de cette classe + private val _taskList = MutableLiveData<List<Task>>() + // [taskList] est publique mais non-modifiable: + // On pourra seulement l'observer (s'y abonner) depuis d'autres classes + public val taskList: LiveData<List<Task>> = _taskList + + suspend fun refresh() { + // Call HTTP (opération longue): + val tasksResponse = tasksWebService.getTasks() + // À la ligne suivante, on a reçu la réponse de l'API: + if (tasksResponse.isSuccessful) { + val fetchedTasks = tasksResponse.body() + // on modifie la valeur encapsulée, ce qui va notifier ses Observers et donc déclencher leur callback + _taskList.value = fetchedTasks!! + } + } + + suspend fun deleteTask(task : Task){ + val taskResponse = tasksWebService.deleteTask(id = task.id) + if (taskResponse.isSuccessful){ + val editableList = _taskList.value.orEmpty().toMutableList() + val position = editableList.indexOfFirst { task.id == it.id } + editableList.removeAt(position) + _taskList.value = editableList + } + } + + suspend fun addTask(task: Task){ + val taskResponse = tasksWebService.createTask(task) + if (taskResponse.isSuccessful){ + val editableList = _taskList.value.orEmpty().toMutableList() + editableList.add(taskResponse.body()!!) + _taskList.value = editableList + } + } + + suspend fun updateTask(task: Task) { + // TODO: do update request and check response + // DONE + val taskResponse = tasksWebService.updateTask(task, task.id) + if (taskResponse.isSuccessful){ + val editableList = _taskList.value.orEmpty().toMutableList() + val updatedTask = taskResponse.body() + val position = editableList.indexOfFirst { updatedTask?.id == it.id } + editableList[position] = updatedTask!! + _taskList.value = editableList + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/clemhaowen/dm_td2/network/TasksWebService.kt b/app/src/main/java/com/clemhaowen/dm_td2/network/TasksWebService.kt new file mode 100644 index 0000000000000000000000000000000000000000..02a78ade1f0f597227fda1155bfdb2c50bd4b816 --- /dev/null +++ b/app/src/main/java/com/clemhaowen/dm_td2/network/TasksWebService.kt @@ -0,0 +1,23 @@ +package com.clemhaowen.dm_td2.network + +import android.net.Uri +import androidx.core.net.toFile +import com.clemhaowen.dm_td2.tasklist.Task +import okhttp3.MultipartBody +import okhttp3.RequestBody.Companion.asRequestBody +import retrofit2.Response +import retrofit2.http.* + +interface TasksWebService { + @GET("tasks") + suspend fun getTasks(): Response<List<Task>> + + @DELETE("tasks/{id}") + suspend fun deleteTask(@Path("id") id: String?): Response<Unit> + + @POST("tasks") + suspend fun createTask(@Body task: Task): Response<Task> + + @PATCH("tasks/{id}") + suspend fun updateTask(@Body task: Task, @Path("id") id: String? = task.id): Response<Task> +} \ No newline at end of file diff --git a/app/src/main/java/com/clemhaowen/dm_td2/network/UserInfo.kt b/app/src/main/java/com/clemhaowen/dm_td2/network/UserInfo.kt index aa416ff46088ea288a31914e0a4120faa68d0886..70e0a894c38d9a83836f7fce60abf8c5b86ce171 100644 --- a/app/src/main/java/com/clemhaowen/dm_td2/network/UserInfo.kt +++ b/app/src/main/java/com/clemhaowen/dm_td2/network/UserInfo.kt @@ -10,5 +10,7 @@ data class UserInfo ( @SerialName("firstname") val firstName: String, @SerialName("lastname") - val lastName: String + val lastName: String, + @SerialName("avatar") + val avatar: String = "" ) \ No newline at end of file diff --git a/app/src/main/java/com/clemhaowen/dm_td2/network/UserService.kt b/app/src/main/java/com/clemhaowen/dm_td2/network/UserService.kt index df3803f8ca9eec7f0189d23a2426100e400c7e3a..7306bc419b1f141f4bfe202acafda7e484a865f5 100644 --- a/app/src/main/java/com/clemhaowen/dm_td2/network/UserService.kt +++ b/app/src/main/java/com/clemhaowen/dm_td2/network/UserService.kt @@ -1,9 +1,17 @@ package com.clemhaowen.dm_td2.network +import okhttp3.MultipartBody import retrofit2.Response import retrofit2.http.GET +import retrofit2.http.Multipart +import retrofit2.http.PATCH +import retrofit2.http.Part interface UserService { @GET("users/info") suspend fun getInfo(): Response<UserInfo> + + @Multipart + @PATCH("users/update_avatar") + suspend fun updateAvatar(@Part avatar: MultipartBody.Part): Response<UserInfo> } \ No newline at end of file diff --git a/app/src/main/java/com/clemhaowen/dm_td2/task/TaskActivity.kt b/app/src/main/java/com/clemhaowen/dm_td2/task/TaskActivity.kt index 4164e9007d5e201a2d73dac68e8a55b260a6a29c..ad6dcf83e30a56549e0817e852ad16afdeebaff1 100644 --- a/app/src/main/java/com/clemhaowen/dm_td2/task/TaskActivity.kt +++ b/app/src/main/java/com/clemhaowen/dm_td2/task/TaskActivity.kt @@ -21,7 +21,7 @@ class TaskActivity : AppCompatActivity() { createButton.setOnClickListener{ val titleText = findViewById<EditText>(R.id.titleEditText).text val descriptionText = findViewById<EditText>(R.id.descriptionEditText).text - val newTask = Task(id = UUID.randomUUID().toString(), title = titleText.toString(), description = descriptionText.toString()) + val newTask = Task(id = java.util.UUID.randomUUID().toString(), title = titleText.toString(), description = descriptionText.toString()) intent.putExtra("newTask", newTask) setResult(666, intent) TASK_KEY = "newTask" diff --git a/app/src/main/java/com/clemhaowen/dm_td2/tasklist/Task.kt b/app/src/main/java/com/clemhaowen/dm_td2/tasklist/Task.kt index 217e5ef32730da1ae6cf565b4b562141dcdd4391..9088a4cadd33cd6438700dd441ee40f95034a443 100644 --- a/app/src/main/java/com/clemhaowen/dm_td2/tasklist/Task.kt +++ b/app/src/main/java/com/clemhaowen/dm_td2/tasklist/Task.kt @@ -1,9 +1,15 @@ package com.clemhaowen.dm_td2.tasklist -import java.io.Serializable +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable data class Task ( - var id: String = "id", - var title: String = "Task", - var description: String = "description" -) : Serializable \ No newline at end of file + @SerialName("id") + val id: String = "id", + @SerialName("title") + val title: String = "Task", + @SerialName("description") + val description: String = "description" +) : java.io.Serializable \ No newline at end of file diff --git a/app/src/main/java/com/clemhaowen/dm_td2/tasklist/TaskAdapter.kt b/app/src/main/java/com/clemhaowen/dm_td2/tasklist/TaskAdapter.kt index 7f7e3c9bc4573efd19ab9869112bde766ac0aa62..94e8069fa3c75ff33800e97a18ed9453f98d43fb 100644 --- a/app/src/main/java/com/clemhaowen/dm_td2/tasklist/TaskAdapter.kt +++ b/app/src/main/java/com/clemhaowen/dm_td2/tasklist/TaskAdapter.kt @@ -9,9 +9,10 @@ import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import com.clemhaowen.dm_td2.R -class TaskListAdapter(private val taskList: List<Task>) : RecyclerView.Adapter<TaskListAdapter.TaskViewHolder>() { +class TaskListAdapter(/*private val*/var taskList: List<Task>) : RecyclerView.Adapter<TaskListAdapter.TaskViewHolder>() { var onDeleteClickListener: ((Task) -> Unit)? = null + var onEditClickListener: ((Task) -> Unit)? = null override fun getItemCount(): Int = taskList.size @@ -33,6 +34,12 @@ class TaskListAdapter(private val taskList: List<Task>) : RecyclerView.Adapter<T val deleteButton = findViewById<ImageButton>(R.id.delete_task) deleteButton.setOnClickListener { onDeleteClickListener?.invoke(taskTitle)} + val editButton = findViewById<ImageButton>(R.id.editButton) + editButton.setOnClickListener { + // TODO invoke editClickListener + // DONE + onEditClickListener?.invoke(taskTitle) + } } diff --git a/app/src/main/java/com/clemhaowen/dm_td2/tasklist/TaskListFragment.kt b/app/src/main/java/com/clemhaowen/dm_td2/tasklist/TaskListFragment.kt index bf884d0bd7907e9007736e258f942382a189f3c2..d9a4c98e4a0595dd6498f58424f91e88a116370a 100644 --- a/app/src/main/java/com/clemhaowen/dm_td2/tasklist/TaskListFragment.kt +++ b/app/src/main/java/com/clemhaowen/dm_td2/tasklist/TaskListFragment.kt @@ -5,13 +5,23 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.ImageButton +import android.widget.ImageView +import android.widget.TextView import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.observe import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import coil.load +import coil.transform.CircleCropTransformation +import coil.transform.RoundedCornersTransformation import com.clemhaowen.dm_td2.R +import com.clemhaowen.dm_td2.network.Api +import com.clemhaowen.dm_td2.network.TasksRepository import com.clemhaowen.dm_td2.task.TaskActivity +import com.clemhaowen.dm_td2.userInfo.UserInfoActivity import com.google.android.material.floatingactionbutton.FloatingActionButton +import kotlinx.coroutines.launch import java.util.* class TaskListFragment : Fragment() { @@ -24,6 +34,8 @@ class TaskListFragment : Fragment() { Task(id = "id_3", title = "Task 3") ) + private val tasksRepository = TasksRepository() + companion object { const val ADD_TASK_REQUEST_CODE = 666 } @@ -37,16 +49,38 @@ class TaskListFragment : Fragment() { } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - var recyclerView = view?.findViewById<RecyclerView>(R.id.fragment_tasklist_recyclerView) + var recyclerView = view.findViewById<RecyclerView>(R.id.fragment_tasklist_recyclerView) recyclerView?.layoutManager = LinearLayoutManager(activity) + // En utilisant les synthetics, on écrit juste l'id directement (c'est magique ✨): + //R.id.fragment_tasklist_recyclerView.layoutManager = LinearLayoutManager(activity) // ça marche pas + // TODO uncomment when TaskListAdapter is created + // DONE recyclerView?.adapter = TaskListAdapter(taskList) (recyclerView?.adapter as TaskListAdapter).onDeleteClickListener = { task -> // Supprimer la tâche - taskList.remove(task) - (recyclerView?.adapter as TaskListAdapter).notifyDataSetChanged() + //taskList.remove(task) + //(recyclerView?.adapter as TaskListAdapter).notifyDataSetChanged() + lifecycleScope.launch { + tasksRepository.deleteTask(task) + } } - val createButton = view?.findViewById<FloatingActionButton>(R.id.floatingActionButton) + + (recyclerView?.adapter as TaskListAdapter).onEditClickListener = { task -> + lifecycleScope.launch { + tasksRepository.updateTask(task) + } + } + + tasksRepository.taskList.observe(viewLifecycleOwner, androidx.lifecycle.Observer { + val editableList = (recyclerView?.adapter as TaskListAdapter).taskList.toMutableList() + editableList.clear() + editableList.addAll(it) + (recyclerView?.adapter as TaskListAdapter).taskList = editableList + (recyclerView?.adapter as TaskListAdapter).notifyDataSetChanged() + }) + + val createButton = view.findViewById<FloatingActionButton>(R.id.floatingActionButton) createButton.setOnClickListener { val intent = Intent(activity, TaskActivity::class.java) startActivityForResult(intent, ADD_TASK_REQUEST_CODE) @@ -54,12 +88,33 @@ class TaskListFragment : Fragment() { (recyclerView?.adapter as TaskListAdapter).notifyDataSetChanged() } - // En utilisant les synthetics, on écrit juste l'id directement (c'est magique ✨): - //R.id.fragment_tasklist_recyclerView.layoutManager = LinearLayoutManager(activity) // ça marche pas + val profilPhoto = view.findViewById<ImageView>(R.id.imageViewProfilPhoto) + profilPhoto.setOnClickListener { + val intent = Intent(activity, UserInfoActivity::class.java) + startActivity(intent) + } + } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { val task = data!!.getSerializableExtra(TaskActivity.TASK_KEY) as Task taskList.add(task) } + + override fun onResume() { + super.onResume() + val profilPhoto = view?.findViewById<ImageView>(R.id.imageViewProfilPhoto) + profilPhoto?.load("https://goo.gl/gEgYUd") { + crossfade(true) + transformations(CircleCropTransformation()) + } + lifecycleScope.launch { + tasksRepository.refresh() + } + lifecycleScope.launch { + val userInfo = Api.userService.getInfo().body()!! + val textView = view?.findViewById<TextView>(R.id.textViewInfo) + textView?.text = "${userInfo.firstName} ${userInfo.lastName}" + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/clemhaowen/dm_td2/userInfo/UserInfoActivity.kt b/app/src/main/java/com/clemhaowen/dm_td2/userInfo/UserInfoActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..7080a8dc94eb52ababf6115f130bbe0e379c7966 --- /dev/null +++ b/app/src/main/java/com/clemhaowen/dm_td2/userInfo/UserInfoActivity.kt @@ -0,0 +1,139 @@ +package com.clemhaowen.dm_td2.userInfo + +import android.Manifest +import android.content.pm.PackageManager +import android.graphics.Bitmap +import android.net.Uri +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.widget.Button +import android.widget.ImageView +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts +import androidx.activity.result.launch +import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContextCompat +import androidx.core.content.FileProvider +import androidx.core.net.toFile +import androidx.core.net.toUri +import androidx.lifecycle.lifecycleScope +import coil.load +import com.clemhaowen.dm_td2.BuildConfig +import com.clemhaowen.dm_td2.R +import com.clemhaowen.dm_td2.network.Api +import kotlinx.coroutines.launch +import okhttp3.MultipartBody +import okhttp3.RequestBody.Companion.asRequestBody +import okhttp3.RequestBody.Companion.toRequestBody +import java.io.File + +class UserInfoActivity : AppCompatActivity() { + private val userService = Api.userService + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_user_info) + val buttonToTakePicture = findViewById<Button>(R.id.take_picture_button) + buttonToTakePicture.setOnClickListener{ + askCameraPermissionAndOpenCamera() + } + lifecycleScope.launch { + val userInfoResponse = userService.getInfo() + if (userInfoResponse.isSuccessful){ + val imageView = findViewById<ImageView>(R.id.image_view) + imageView.load(userInfoResponse.body()?.avatar) + } + } + } + + private val requestPermissionLauncher = + registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean -> + if (isGranted) openCamera() + else showExplanationDialog() + } + + private fun requestCameraPermission() = + requestPermissionLauncher.launch(Manifest.permission.CAMERA) + + private fun askCameraPermissionAndOpenCamera() { + when { + ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) + == PackageManager.PERMISSION_GRANTED -> openCamera() + shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -> showExplanationDialog() + else -> requestCameraPermission() + } + } + + private fun showExplanationDialog() { + AlertDialog.Builder(this).apply { + setMessage("On a besoin de la caméra sivouplé ! 🥺") + setPositiveButton("Bon, ok") { _, _ -> + requestCameraPermission() + } + setCancelable(true) + show() + } + } + + // convert + private fun convert(uri: Uri) = + MultipartBody.Part.createFormData( + name = "avatar", + filename = "temp.jpeg", + body = contentResolver.openInputStream(uri)!!.readBytes().toRequestBody() + ) + + private suspend fun handleImage(photoUri: Uri){ + userService.updateAvatar(convert(photoUri)) + } + + // use + private fun openCamera() = takePicture.launch() + + // register + private val takePicture = registerForActivityResult(ActivityResultContracts.TakePicturePreview()) { bitmap -> + val tmpFile = File.createTempFile("avatar", "jpeg") + tmpFile.outputStream().use { + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it) + } + lifecycleScope.launch { handleImage(tmpFile.toUri()) } + } + + + + + + + + /* + // create a temp file and get a uri for it + private val photoUri = FileProvider.getUriForFile( + this, + BuildConfig.APPLICATION_ID +".fileProvider", + File.createTempFile("avatar", "jpeg") + ) + + // register + private val takePicture = + registerForActivityResult(ActivityResultContracts.TakePicture()) { success -> + if (success) lifecycleScope.launch { handleImage(photoUri) } + else Toast.makeText(this, "Si vous refusez, on peux pas prendre de photo ! 😢", Toast.LENGTH_LONG).show() + } + + // use + private fun openCamera() = takePicture.launch(photoUri) + + // convert + private fun convert(uri: Uri) = + MultipartBody.Part.createFormData( + name = "avatar", + filename = "temp.jpeg", + body = uri.toFile().asRequestBody() + ) + + // register + private val pickInGallery = registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> lifecycleScope.launch { handleImage(uri) } } + + // use*/ + //private fun uploadFromGallery() = pickInGallery.launch("image/*") +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_user_info.xml b/app/src/main/res/layout/activity_user_info.xml new file mode 100644 index 0000000000000000000000000000000000000000..9eb298af903f2d411a8273c79c541a76358a95b0 --- /dev/null +++ b/app/src/main/res/layout/activity_user_info.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".userInfo.UserInfoActivity"> + + <ImageView + android:id="@+id/image_view" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <Button + android:id="@+id/upload_image_button" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Choisir une Image" /> + + <Button + android:id="@+id/take_picture_button" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Prendre une photo" /> +</LinearLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_task_list.xml b/app/src/main/res/layout/fragment_task_list.xml index a73afd55f74804180a9b396eb34897399bbc282a..a08dd0c6590db934a7b2a035cdbecaa4bf9637cf 100644 --- a/app/src/main/res/layout/fragment_task_list.xml +++ b/app/src/main/res/layout/fragment_task_list.xml @@ -6,14 +6,38 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + <ImageView + android:id="@+id/imageViewProfilPhoto" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="5dp" + android:layout_marginTop="5dp" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:srcCompat="@tools:sample/avatars" /> + + <TextView + android:id="@+id/textViewInfo" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="5dp" + android:layout_marginEnd="5dp" + android:text="TextView" + app:layout_constraintBottom_toTopOf="@+id/fragment_tasklist_recyclerView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/imageViewProfilPhoto" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.513" /> + <androidx.recyclerview.widget.RecyclerView android:id="@+id/fragment_tasklist_recyclerView" android:layout_width="0dp" android:layout_height="0dp" + android:layout_marginTop="5dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toBottomOf="@+id/imageViewProfilPhoto" /> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/floatingActionButton" diff --git a/build.gradle b/build.gradle index 5fac855a5d1325a3a18b809c4ab475966a1a4cac..55e96ce110eddfbe44eac101e489e9160583a713 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = "1.3.72" + ext.kotlin_version = "1.4.20" repositories { google() jcenter()