added user credentials implementation

This commit is contained in:
nuclearfog 2023-10-07 21:59:53 +02:00
parent 82621a7c46
commit ce0464ef79
No known key found for this signature in database
GPG Key ID: 03488A185C476379
8 changed files with 378 additions and 119 deletions

View File

@ -12,6 +12,7 @@ import org.nuclearfog.twidda.backend.helper.update.StatusUpdate;
import org.nuclearfog.twidda.backend.helper.update.UserListUpdate;
import org.nuclearfog.twidda.backend.helper.update.UserUpdate;
import org.nuclearfog.twidda.model.Account;
import org.nuclearfog.twidda.model.Credentials;
import org.nuclearfog.twidda.model.Emoji;
import org.nuclearfog.twidda.model.Filter;
import org.nuclearfog.twidda.model.Hashtag;
@ -539,12 +540,19 @@ public interface Connection {
UserList updateUserlist(UserListUpdate update) throws ConnectionException;
/**
* updates current user's profile
* get current user's credentials
*
* @return user credentials
*/
Credentials getCredentials() throws ConnectionException;
/**
* updates current user's credentials
*
* @param update profile update information
* @return updated user information
*/
User updateUser(UserUpdate update) throws ConnectionException;
User updateCredentials(UserUpdate update) throws ConnectionException;
/**
* upload media file and generate a media ID

View File

@ -15,6 +15,7 @@ import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.backend.api.Connection;
import org.nuclearfog.twidda.backend.api.ConnectionException;
import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonAccount;
import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonCredentials;
import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonEmoji;
import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonFilter;
import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonHashtag;
@ -42,6 +43,7 @@ import org.nuclearfog.twidda.backend.utils.ConnectionBuilder;
import org.nuclearfog.twidda.backend.utils.StringUtils;
import org.nuclearfog.twidda.config.GlobalSettings;
import org.nuclearfog.twidda.model.Account;
import org.nuclearfog.twidda.model.Credentials;
import org.nuclearfog.twidda.model.Emoji;
import org.nuclearfog.twidda.model.Filter;
import org.nuclearfog.twidda.model.Hashtag;
@ -220,8 +222,8 @@ public class Mastodon implements Connection {
if (response.code() == 200 && body != null) {
JSONObject json = new JSONObject(body.string());
String bearer = json.getString("access_token");
User user = getCredentials(hostname, bearer);
Account account = new MastodonAccount(user, hostname, bearer, connection.getOauthConsumerToken(), connection.getOauthTokenSecret());
Credentials credentials = getCredentials(hostname, bearer);
Account account = new MastodonAccount(credentials.getId(), hostname, bearer, connection.getOauthConsumerToken(), connection.getOauthTokenSecret());
settings.setLogin(account, false);
return account;
}
@ -1139,7 +1141,13 @@ public class Mastodon implements Connection {
@Override
public User updateUser(UserUpdate update) throws MastodonException {
public Credentials getCredentials() throws ConnectionException {
return getCredentials(settings.getLogin().getHostname(), settings.getLogin().getBearerToken());
}
@Override
public User updateCredentials(UserUpdate update) throws MastodonException {
List<String> params = new ArrayList<>();
List<InputStream> streams = new ArrayList<>();
List<String> keys = new ArrayList<>();
@ -1346,13 +1354,13 @@ public class Mastodon implements Connection {
* @param bearer bearer token to use
* @return current user information
*/
private User getCredentials(String host, @NonNull String bearer) throws MastodonException {
private Credentials getCredentials(String host, @NonNull String bearer) throws MastodonException {
try {
Response response = get(host, ENDPOINT_VERIFY_CREDENTIALS, bearer, new ArrayList<>());
ResponseBody body = response.body();
if (response.code() == 200 && body != null) {
JSONObject json = new JSONObject(body.string());
return new MastodonUser(json);
return new MastodonCredentials(json);
}
throw new MastodonException(response);
} catch (IOException | JSONException e) {

View File

@ -23,23 +23,20 @@ public class MastodonAccount implements Account {
private String bearer;
private String client_id, client_secret;
private User user;
/**
* @param user user information
* @param id user ID
* @param hostname hostname of the Mastodon isntance
* @param bearer bearer token
* @param client_id app client ID
* @param client_secret app client secret
*/
public MastodonAccount(User user, String hostname, String bearer, String client_id, String client_secret) {
this.user = user;
public MastodonAccount(long id, String hostname, String bearer, String client_id, String client_secret) {
this.id = id;
this.hostname = hostname;
this.bearer = bearer;
this.client_id = client_id;
this.client_secret = client_secret;
timestamp = System.currentTimeMillis();
id = user.getId();
}
@ -58,7 +55,7 @@ public class MastodonAccount implements Account {
@Nullable
@Override
public User getUser() {
return user;
return null;
}
@ -107,7 +104,7 @@ public class MastodonAccount implements Account {
@NonNull
@Override
public String toString() {
return "hostname=\"" + getHostname() + "\" configuration=\"" + getConfiguration().getName() + "\" " + user;
return "hostname=\"" + getHostname() + "\" configuration=\"" + getConfiguration().getName() + "\" id=" + id;
}

View File

@ -0,0 +1,105 @@
package org.nuclearfog.twidda.backend.api.mastodon.impl;
import org.json.JSONException;
import org.json.JSONObject;
import org.nuclearfog.twidda.model.Credentials;
import org.nuclearfog.twidda.model.User.Field;
/**
* Mastodon implementation of {@link Credentials}
*
* @author nuclearfog
*/
public class MastodonCredentials implements Credentials {
private static final long serialVersionUID = 4255988093112848364L;
private long id;
private String username;
private String description;
private String language;
private int visibility;
private boolean sensitive;
/**
* @param json Credentials json format
*/
public MastodonCredentials(JSONObject json) throws JSONException {
JSONObject sourceJson = json.getJSONObject("source");
String idStr = json.getString("id");
String visStr = sourceJson.optString("privacy", "");
username = json.getString("display_name");
description = sourceJson.getString("note");
language = sourceJson.getString("language");
sensitive = sourceJson.getBoolean("sensitive");
switch(visStr) {
case "public":
visibility = PUBLIC;
break;
case "private":
visibility = PRIVATE;
break;
case "direct":
visibility = DIRECT;
break;
case "unlisted":
visibility = UNLISTED;
break;
default:
visibility = DEFAULT;
break;
}
try {
id = Long.parseLong(idStr);
} catch (NumberFormatException e) {
throw new JSONException("bad user ID:" + idStr);
}
}
@Override
public long getId() {
return id;
}
@Override
public String getUsername() {
return username;
}
@Override
public String getDescription() {
return description;
}
@Override
public String getLanguage() {
return language;
}
@Override
public int getVisibility() {
return visibility;
}
@Override
public boolean isSensitive() {
return sensitive;
}
@Override
public Field[] getFields() {
return new Field[0];
}
}

View File

@ -0,0 +1,98 @@
package org.nuclearfog.twidda.backend.async;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.nuclearfog.twidda.backend.api.Connection;
import org.nuclearfog.twidda.backend.api.ConnectionException;
import org.nuclearfog.twidda.backend.api.ConnectionManager;
import org.nuclearfog.twidda.backend.helper.update.UserUpdate;
import org.nuclearfog.twidda.database.AppDatabase;
import org.nuclearfog.twidda.model.Credentials;
import org.nuclearfog.twidda.model.User;
/**
* loader class for user credentials
*
* @author nuclearfog
* @see org.nuclearfog.twidda.ui.activities.ProfileEditor
*/
public class CredentialsAction extends AsyncExecutor<CredentialsAction.Param, CredentialsAction.Result> {
private Connection connection;
private AppDatabase db;
/**
*
*/
public CredentialsAction(Context context) {
db = new AppDatabase(context);
connection = ConnectionManager.getDefaultConnection(context);
}
@Override
protected Result doInBackground(@NonNull Param param) {
try {
if (param.mode == Param.UPDATE) {
if (param.update != null) {
User user = connection.updateCredentials(param.update);
db.saveUser(user);
param.update.close();
return new Result(Result.UPDATE, user, null, null);
}
} else if (param.mode == Param.LOAD) {
Credentials credentials = connection.getCredentials();
return new Result(Result.LOAD, null, credentials, null);
}
} catch (ConnectionException exception) {
return new Result(Result.ERROR, null, null, exception);
}
return null;
}
/**
*
*/
public static class Param {
public static final int LOAD = 1;
public static final int UPDATE = 2;
final int mode;
@Nullable
final UserUpdate update;
public Param(int mode, @Nullable UserUpdate update) {
this.mode = mode;
this.update = update;
}
}
/**
*
*/
public static class Result {
public static final int ERROR = -1;
public static final int LOAD = 10;
public static final int UPDATE = 20;
public final int mode;
@Nullable
public final User user;
@Nullable
public final Credentials credentials;
@Nullable
public final ConnectionException exception;
Result(int mode, @Nullable User user, @Nullable Credentials credentials, @Nullable ConnectionException exception) {
this.mode = mode;
this.user = user;
this.credentials = credentials;
this.exception = exception;
}
}
}

View File

@ -1,64 +0,0 @@
package org.nuclearfog.twidda.backend.async;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.nuclearfog.twidda.backend.api.Connection;
import org.nuclearfog.twidda.backend.api.ConnectionException;
import org.nuclearfog.twidda.backend.api.ConnectionManager;
import org.nuclearfog.twidda.backend.helper.update.UserUpdate;
import org.nuclearfog.twidda.database.AppDatabase;
import org.nuclearfog.twidda.model.User;
import org.nuclearfog.twidda.ui.activities.ProfileEditor;
/**
* Async loader to update user profile
*
* @author nuclearfog
* @see ProfileEditor
*/
public class UserUpdater extends AsyncExecutor<UserUpdate, UserUpdater.Result> {
private Connection connection;
private AppDatabase db;
/**
*
*/
public UserUpdater(Context context) {
db = new AppDatabase(context);
connection = ConnectionManager.getDefaultConnection(context);
}
@Override
protected Result doInBackground(@NonNull UserUpdate param) {
try {
User user = connection.updateUser(param);
db.saveUser(user);
return new Result(user, null);
} catch (ConnectionException exception) {
return new Result(null, exception);
} finally {
param.close();
}
}
/**
*
*/
public static class Result {
@Nullable
public final User user;
@Nullable
public final ConnectionException exception;
Result(@Nullable User user, @Nullable ConnectionException exception) {
this.user = user;
this.exception = exception;
}
}
}

View File

@ -0,0 +1,64 @@
package org.nuclearfog.twidda.model;
import java.io.Serializable;
import org.nuclearfog.twidda.model.User.Field;
/**
* represents credentials of the current user
*
* @author nuclearfog
*/
public interface Credentials extends Serializable {
int DEFAULT = 0;
int PUBLIC = 10;
int PRIVATE = 11;
int DIRECT = 12;
int UNLISTED = 13;
/**
* get user ID
*
* @return user ID
*/
long getId();
/**
* get user display name
*/
String getUsername();
/**
* get user description
*/
String getDescription();
/**
* get default language for posting a status
*
* @return ISO 639 Part 1 two-letter language code or empty
*/
String getLanguage();
/**
* get default visibility of the user's status
*
* @return {@link #PUBLIC,#PRIVATE,#DIRECT,#UNLISTED}
*/
int getVisibility();
/**
* get default sensitive setting for posts
*/
boolean isSensitive();
/**
* get user fields
*/
Field[] getFields();
}

View File

@ -6,6 +6,8 @@ import android.content.res.Resources;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
@ -28,7 +30,7 @@ import com.squareup.picasso.Transformation;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.backend.async.AsyncExecutor.AsyncCallback;
import org.nuclearfog.twidda.backend.async.UserUpdater;
import org.nuclearfog.twidda.backend.async.CredentialsAction;
import org.nuclearfog.twidda.backend.helper.MediaStatus;
import org.nuclearfog.twidda.backend.helper.update.UserUpdate;
import org.nuclearfog.twidda.backend.image.PicassoBuilder;
@ -37,6 +39,7 @@ import org.nuclearfog.twidda.backend.utils.ErrorUtils;
import org.nuclearfog.twidda.backend.utils.ToolbarUpdater;
import org.nuclearfog.twidda.config.Configuration;
import org.nuclearfog.twidda.config.GlobalSettings;
import org.nuclearfog.twidda.model.Credentials;
import org.nuclearfog.twidda.model.User;
import org.nuclearfog.twidda.ui.dialogs.ConfirmDialog;
import org.nuclearfog.twidda.ui.dialogs.ConfirmDialog.OnConfirmListener;
@ -51,7 +54,7 @@ import jp.wasabeef.picasso.transformations.RoundedCornersTransformation;
*
* @author nuclearfog
*/
public class ProfileEditor extends MediaActivity implements OnClickListener, AsyncCallback<UserUpdater.Result>, OnConfirmListener, Callback {
public class ProfileEditor extends MediaActivity implements OnClickListener, AsyncCallback<CredentialsAction.Result>, OnConfirmListener, TextWatcher, Callback {
/**
* key to preload user data
@ -64,7 +67,7 @@ public class ProfileEditor extends MediaActivity implements OnClickListener, Asy
*/
public static final int RETURN_PROFILE_UPDATED = 0xF5C0E570;
private UserUpdater editorAsync;
private CredentialsAction credentialAction;
private GlobalSettings settings;
private Picasso picasso;
@ -77,7 +80,10 @@ public class ProfileEditor extends MediaActivity implements OnClickListener, Asy
@Nullable
private User user;
@Nullable
private Credentials credentials;
private UserUpdate userUpdate = new UserUpdate();
private boolean changed = false;
@Override
@ -106,7 +112,7 @@ public class ProfileEditor extends MediaActivity implements OnClickListener, Asy
progressDialog = new ProgressDialog(this, null);
confirmDialog = new ConfirmDialog(this, this);
editorAsync = new UserUpdater(this);
credentialAction = new CredentialsAction(this);
settings = GlobalSettings.get(this);
picasso = PicassoBuilder.get(this);
@ -136,35 +142,43 @@ public class ProfileEditor extends MediaActivity implements OnClickListener, Asy
Object data = getIntent().getSerializableExtra(KEY_USER);
if (data instanceof User) {
setUser((User) data);
user = (User) data;
setUser();
}
username.addTextChangedListener(this);
profileUrl.addTextChangedListener(this);
profileLocation.addTextChangedListener(this);
userDescription.addTextChangedListener(this);
profile_image.setOnClickListener(this);
profile_banner.setOnClickListener(this);
addBannerBtn.setOnClickListener(this);
}
@Override
protected void onStart() {
super.onStart();
if (credentials == null) {
CredentialsAction.Param param = new CredentialsAction.Param(CredentialsAction.Param.LOAD, null);
credentialAction.execute(param, this);
}
}
@Override
protected void onDestroy() {
progressDialog.dismiss();
editorAsync.cancel();
credentialAction.cancel();
super.onDestroy();
}
@Override
public void onBackPressed() {
String username = this.username.getText().toString();
String userLink = profileUrl.getText().toString();
String userLoc = profileLocation.getText().toString();
String userBio = userDescription.getText().toString();
if (user != null && username.equals(user.getUsername()) && userLink.equals(user.getProfileUrl())
&& userLoc.equals(user.getLocation()) && userBio.equals(user.getDescription()) && !userUpdate.imageAdded()) {
super.onBackPressed();
} else if (username.isEmpty() && userLink.isEmpty() && userLoc.isEmpty() && userBio.isEmpty()) {
super.onBackPressed();
} else {
if (changed) {
confirmDialog.show(ConfirmDialog.PROFILE_EDITOR_LEAVE);
} else {
super.onBackPressed();
}
}
@ -227,6 +241,22 @@ public class ProfileEditor extends MediaActivity implements OnClickListener, Asy
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
changed = true;
}
@Override
public void onConfirm(int type, boolean remember) {
// leave without settings
@ -241,14 +271,17 @@ public class ProfileEditor extends MediaActivity implements OnClickListener, Asy
@Override
public void onResult(@NonNull UserUpdater.Result result) {
if (result.user != null) {
public void onResult(@NonNull CredentialsAction.Result result) {
if (result.mode == CredentialsAction.Result.UPDATE) {
Intent data = new Intent();
data.putExtra(KEY_USER, result.user);
Toast.makeText(getApplicationContext(), R.string.info_profile_updated, Toast.LENGTH_SHORT).show();
setResult(RETURN_PROFILE_UPDATED, data);
finish();
} else {
} else if (result.mode == CredentialsAction.Result.LOAD) {
credentials = result.credentials;
setCredentials();
} else if (result.mode == CredentialsAction.Result.ERROR) {
String message = ErrorUtils.getErrorMessage(this, result.exception);
confirmDialog.show(ConfirmDialog.PROFILE_EDITOR_ERROR, message);
progressDialog.dismiss();
@ -273,7 +306,7 @@ public class ProfileEditor extends MediaActivity implements OnClickListener, Asy
* update user information
*/
private void updateUser() {
if (editorAsync.isIdle()) {
if (credentialAction.isIdle()) {
String username = this.username.getText().toString();
String userLoc = profileLocation.getText().toString();
String userBio = userDescription.getText().toString();
@ -283,7 +316,8 @@ public class ProfileEditor extends MediaActivity implements OnClickListener, Asy
} else {
userUpdate.setProfile(username, userBio, userLoc);
if (userUpdate.prepare(getContentResolver())) {
editorAsync.execute(userUpdate, this);
CredentialsAction.Param param = new CredentialsAction.Param(CredentialsAction.Param.UPDATE, userUpdate);
credentialAction.execute(param, this);
progressDialog.show();
} else {
Toast.makeText(getApplicationContext(), R.string.error_media_init, Toast.LENGTH_SHORT).show();
@ -295,25 +329,34 @@ public class ProfileEditor extends MediaActivity implements OnClickListener, Asy
/**
* Set current user's information
*/
private void setUser(User user) {
String profileImageUrl = user.getProfileImageThumbnailUrl();
String bannerImageUrl = user.getBannerImageThumbnailUrl();
if (!profileImageUrl.isEmpty()) {
Transformation roundCorner = new RoundedCornersTransformation(5, 0);
picasso.load(profileImageUrl).transform(roundCorner).into(profile_image);
private void setUser() {
if (user != null) {
String profileImageUrl = user.getProfileImageThumbnailUrl();
String bannerImageUrl = user.getBannerImageThumbnailUrl();
if (!profileImageUrl.isEmpty()) {
Transformation roundCorner = new RoundedCornersTransformation(5, 0);
picasso.load(profileImageUrl).transform(roundCorner).into(profile_image);
}
if (!bannerImageUrl.isEmpty()) {
picasso.load(bannerImageUrl).into(profile_banner, this);
addBannerBtn.setVisibility(View.INVISIBLE);
changeBannerBtn.setVisibility(View.VISIBLE);
} else {
addBannerBtn.setVisibility(View.VISIBLE);
changeBannerBtn.setVisibility(View.INVISIBLE);
}
username.setText(user.getUsername());
profileUrl.setText(user.getProfileUrl());
profileLocation.setText(user.getLocation());
userDescription.setText(user.getDescription());
}
if (!bannerImageUrl.isEmpty()) {
picasso.load(bannerImageUrl).into(profile_banner, this);
addBannerBtn.setVisibility(View.INVISIBLE);
changeBannerBtn.setVisibility(View.VISIBLE);
} else {
addBannerBtn.setVisibility(View.VISIBLE);
changeBannerBtn.setVisibility(View.INVISIBLE);
}
username.setText(user.getUsername());
profileUrl.setText(user.getProfileUrl());
profileLocation.setText(user.getLocation());
userDescription.setText(user.getDescription());
this.user = user;
}
/**
*
* set current user's credentials
*/
private void setCredentials() {
}
}