Allow to edit profile + profile picture

This commit is contained in:
Thomas 2020-11-01 18:34:53 +01:00
parent fdc6e6e325
commit 0f38ad180a
11 changed files with 207 additions and 27 deletions

View File

@ -105,6 +105,7 @@ dependencies {
implementation ("androidx.navigation:navigation-dynamic-features-fragment:2.3.1")
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.browser:browser:1.2.0'
implementation 'androidx.documentfile:documentfile:1.0.1'
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

View File

@ -13,6 +13,8 @@
<string name="set_autoplay_next_video_choice" translatable="false">set_autoplay_next_video_choice</string>
<string name="set_store_in_history">set_store_in_history</string>
<string name="change_profile_picture">Modifier la photo de profil</string>
<string name="account_updated">Le compte a été mis à jour !</string>
<string name="save">Enregistrer</string>
<string name="set_autoplay_next_video">Lire automatiquement la vidéo suivante</string>

View File

@ -14,7 +14,7 @@
<string name="save">Save</string>
<string name="enable_history">Enable history</string>
<string name="change_profile_picture">Change profile picture</string>
<string name="set_autoplay">Automatic playback</string>
<string name="set_autoplay_description">If enabled, videos will be played automatically</string>
@ -87,6 +87,7 @@
<string name="download_file">Download %1$s</string>
<string name="account_updated">The account has been updated!</string>
<string name="action_privacy">Privacy</string>
<string name="action_logout">Logout</string>

View File

@ -18,7 +18,7 @@
<application
android:name=".FedilabTube"
android:allowBackup="false"
android:requestLegacyExternalStorage="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"

View File

@ -1,17 +1,37 @@
package app.fedilab.fedilabtube;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.documentfile.provider.DocumentFile;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.request.RequestOptions;
import app.fedilab.fedilabtube.client.RetrofitPeertubeAPI;
import app.fedilab.fedilabtube.client.entities.Error;
import app.fedilab.fedilabtube.client.entities.UserMe;
import app.fedilab.fedilabtube.client.entities.UserSettings;
import app.fedilab.fedilabtube.databinding.ActivityMyAccountSettingsBinding;
import app.fedilab.fedilabtube.helper.Helper;
import es.dmoral.toasty.Toasty;
import static app.fedilab.fedilabtube.PeertubeUploadActivity.MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE;
/* Copyright 2020 Thomas Schneider
*
* This file is a part of TubeLab
@ -30,6 +50,9 @@ import es.dmoral.toasty.Toasty;
public class MyAccountActivity extends AppCompatActivity {
ActivityMyAccountSettingsBinding binding;
private static final int PICK_IMAGE = 466;
private Uri inputData;
private String fileName;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -48,19 +71,65 @@ public class MyAccountActivity extends AppCompatActivity {
binding.displayname.setText(MainActivity.userMe.getAccount().getDisplayName());
binding.description.setText(MainActivity.userMe.getAccount().getDescription());
binding.save.setOnClickListener(v -> new Thread(() -> {
Helper.loadGiF(MyAccountActivity.this, MainActivity.userMe.getAccount().getAvatar()!=null?MainActivity.userMe.getAccount().getAvatar().getPath():null, binding.profilePicture);
binding.selectFile.setOnClickListener(v->{
if (ContextCompat.checkSelfPermission(MyAccountActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) !=
PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MyAccountActivity.this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE);
return;
}
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
String[] mimetypes = {"image/*"};
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimetypes);
startActivityForResult(intent, PICK_IMAGE);
});
binding.save.setOnClickListener(v -> {
binding.save.setEnabled(false);
new Thread(() -> {
UserSettings userSettings = new UserSettings();
if( binding.displayname.getText() != null) {
userSettings.setDisplayName(binding.displayname.getText().toString().trim());
}
if( binding.description.getText() != null) {
userSettings.setDescription(binding.description.getText().toString().trim());
}
if( inputData != null ) {
userSettings.setAvatarfile(inputData);
userSettings.setFileName(fileName);
}
try {
RetrofitPeertubeAPI api = new RetrofitPeertubeAPI(MyAccountActivity.this);
api.updateUser(userSettings);
UserMe.AvatarResponse avatarResponse = api.updateUser(userSettings);
MainActivity.userMe.getAccount().setDisplayName(binding.displayname.getText().toString().trim());
MainActivity.userMe.getAccount().setDescription(binding.description.getText().toString().trim());
} catch (Exception | Error e) {
Toasty.error(MyAccountActivity.this, getString(R.string.toast_error), Toasty.LENGTH_LONG).show();
if( avatarResponse != null && avatarResponse.getAvatar() != null ) {
MainActivity.userMe.getAccount().setAvatar(avatarResponse.getAvatar());
}
}).start());
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
Toasty.info(MyAccountActivity.this, getString(R.string.account_updated), Toasty.LENGTH_LONG).show();
binding.save.setEnabled(true);
};
mainHandler.post(myRunnable);
} catch (Exception | Error e) {
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
Toasty.error(MyAccountActivity.this, getString(R.string.toast_error), Toasty.LENGTH_LONG).show();
binding.save.setEnabled(true);
};
mainHandler.post(myRunnable);
}
}).start();
});
}
@Override
@ -71,4 +140,27 @@ public class MyAccountActivity extends AppCompatActivity {
}
return super.onOptionsItemSelected(item);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PICK_IMAGE && resultCode == Activity.RESULT_OK) {
if (data == null || data.getData() == null) {
Toasty.error(MyAccountActivity.this, getString(R.string.toot_select_image_error), Toast.LENGTH_LONG).show();
return;
}
inputData = data.getData();
DocumentFile documentFile = DocumentFile.fromSingleUri(this, inputData);
if (documentFile != null) {
fileName = documentFile.getName();
}
Glide.with(MyAccountActivity.this)
.load(inputData)
.thumbnail(0.1f)
.apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10)))
.into(binding.profilePicture);
}
}
}

View File

@ -67,7 +67,7 @@ public class PeertubeUploadActivity extends AppCompatActivity {
private final int PICK_IVDEO = 52378;
private final int MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 724;
public static final int MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 724;
private Button set_upload_file, set_upload_submit;
private Spinner set_upload_privacy, set_upload_channel;
private TextView set_upload_file_name;

View File

@ -138,7 +138,7 @@ public interface PeertubeService {
@Multipart
@POST("users/me/avatar/pick")
Call<String> updateProfilePicture(
Call<UserMe.AvatarResponse> updateProfilePicture(
@Header("Authorization") String credentials,
@Part MultipartBody.Part avatarfile);

View File

@ -21,9 +21,12 @@ import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.os.Handler;
import android.os.Looper;
import android.webkit.MimeTypeMap;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
@ -451,8 +454,9 @@ public class RetrofitPeertubeAPI {
*
* @param userSettings UserSettings
*/
public void updateUser(UserSettings userSettings) throws IOException, Error {
public UserMe.AvatarResponse updateUser(UserSettings userSettings) throws IOException, Error {
APIResponse apiResponse = new APIResponse();
UserMe.AvatarResponse avatarResponse = null;
PeertubeService peertubeService = init();
Call<String> updateUser = peertubeService.updateUser(getToken(),
userSettings.isVideosHistoryEnabled(),
@ -473,15 +477,45 @@ public class RetrofitPeertubeAPI {
if (response.errorBody() != null) {
error.setError(response.errorBody().string());
} else {
error.setError(_context.getString(R.string.toast_error));
}
throw error;
}
if (userSettings.getAvatarfile() != null) {
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), userSettings.getAvatarfile());
MultipartBody.Part bodyThumbnail = MultipartBody.Part.createFormData("avatarfile", userSettings.getAvatarfile().getName(), requestFile);
Call<String> updateProfilePicture = peertubeService.updateProfilePicture(getToken(), bodyThumbnail);
InputStream inputStream = _context.getContentResolver().openInputStream(userSettings.getAvatarfile());
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int len;
while ((len = inputStream.read(buffer)) != -1) {
byteBuffer.write(buffer, 0, len);
}
byte[] imageBytes = byteBuffer.toByteArray();
String mime = MimeTypeMap.getFileExtensionFromUrl(userSettings.getAvatarfile().toString());
if( mime == null || mime.trim().length() == 0) {
mime = "png";
}
RequestBody requestFile = RequestBody.create(MediaType.parse("image/"+mime), imageBytes);
MultipartBody.Part bodyThumbnail = MultipartBody.Part.createFormData("avatarfile", userSettings.getFileName(), requestFile);
Call<UserMe.AvatarResponse> updateProfilePicture = peertubeService.updateProfilePicture(getToken(), bodyThumbnail);
Response<UserMe.AvatarResponse> responseAvatar = updateProfilePicture.execute();
if (response.isSuccessful()) {
avatarResponse = responseAvatar.body();
} else {
setError(apiResponse, response.code(), response.errorBody());
Error error = new Error();
error.setStatusCode(response.code());
if (response.errorBody() != null) {
error.setError(response.errorBody().string());
} else {
error.setError(_context.getString(R.string.toast_error));
}
throw error;
}
}
return avatarResponse;
}
/**

View File

@ -265,4 +265,18 @@ public class UserMe {
public void setAutoPlayVideo(boolean autoPlayVideo) {
this.autoPlayVideo = autoPlayVideo;
}
public static class AvatarResponse {
@SerializedName("avatar")
private Avatar avatar;
public Avatar getAvatar() {
return avatar;
}
public void setAvatar(Avatar avatar) {
this.avatar = avatar;
}
}
}

View File

@ -14,8 +14,9 @@ package app.fedilab.fedilabtube.client.entities;
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
* see <http://www.gnu.org/licenses>. */
import android.net.Uri;
import java.util.List;
import java.io.File;
@SuppressWarnings({"unused", "RedundantSuppression"})
public class UserSettings {
@ -27,7 +28,8 @@ public class UserSettings {
private List<String> videoLanguages;
private String description;
private String displayName;
private File avatarfile;
private Uri avatarfile;
private String fileName;
public Boolean isVideosHistoryEnabled() {
return videosHistoryEnabled;
@ -77,11 +79,11 @@ public class UserSettings {
this.displayName = displayName;
}
public File getAvatarfile() {
public Uri getAvatarfile() {
return avatarfile;
}
public void setAvatarfile(File avatarfile) {
public void setAvatarfile(Uri avatarfile) {
this.avatarfile = avatarfile;
}
@ -104,4 +106,21 @@ public class UserSettings {
public void setAutoPlayNextVideo(Boolean autoPlayNextVideo) {
this.autoPlayNextVideo = autoPlayNextVideo;
}
public Boolean getAutoPlayNextVideo() {
return autoPlayNextVideo;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
if( fileName == null) {
this.fileName = "avatar.png";
} else {
this.fileName = fileName;
}
}
}

View File

@ -5,17 +5,34 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ImageView
android:layout_marginTop="50dp"
android:id="@+id/profile_picture"
android:layout_marginStart="50dp"
android:layout_width="80dp"
android:layout_height="80dp"
android:contentDescription="@string/profile_picture"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<Button
android:id="@+id/select_file"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="@string/change_profile_picture"
app:layout_constraintStart_toEndOf="@+id/profile_picture"
app:layout_constraintBottom_toBottomOf="@id/profile_picture" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/displayname_container"
android:layout_width="match_parent"
android:layout_marginTop="50dp"
android:layout_marginTop="30dp"
android:layout_height="wrap_content"
android:layout_marginStart="50dp"
android:layout_marginEnd="50dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
app:layout_constraintTop_toBottomOf="@+id/profile_picture">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/displayname"
android:layout_width="match_parent"