added initial v2 support, VideoView fix for pre-Lollipop, fixed memory leak, added option to show user favoriting a tweet, gradle update, added strings

This commit is contained in:
nuclearfog 2021-12-21 16:49:17 +01:00
parent 086234944e
commit 1d0b1daae1
No known key found for this signature in database
GPG Key ID: AA0271FBE406DB98
16 changed files with 220 additions and 105 deletions

View File

@ -6,7 +6,7 @@ plugins {
android { android {
compileSdkVersion 31 compileSdkVersion 31
buildToolsVersion '31.0.0' buildToolsVersion '32.0.0'
defaultConfig { defaultConfig {
applicationId 'org.nuclearfog.twidda' applicationId 'org.nuclearfog.twidda'
@ -35,6 +35,7 @@ android {
} }
packagingOptions { packagingOptions {
exclude '**/twitter4j.properties.template'
exclude '/META-INF/CHANGES' exclude '/META-INF/CHANGES'
exclude '/META-INF/DEPENDENCIES' exclude '/META-INF/DEPENDENCIES'
exclude '/META-INF/README.md' exclude '/META-INF/README.md'
@ -64,6 +65,7 @@ dependencies {
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
implementation 'org.twitter4j:twitter4j-core:4.0.7' implementation 'org.twitter4j:twitter4j-core:4.0.7'
implementation 'com.github.takke:twitter4j-v2:1.0.3'
//noinspection GradleDependency //noinspection GradleDependency
implementation 'com.squareup.picasso:picasso:2.8' implementation 'com.squareup.picasso:picasso:2.8'
implementation 'com.larswerkman:LicenseView:1.1' implementation 'com.larswerkman:LicenseView:1.1'

View File

@ -1,18 +1,5 @@
package org.nuclearfog.twidda.activities; package org.nuclearfog.twidda.activities;
import static android.media.MediaPlayer.MEDIA_ERROR_UNKNOWN;
import static android.media.MediaPlayer.MEDIA_INFO_BUFFERING_END;
import static android.media.MediaPlayer.MEDIA_INFO_BUFFERING_START;
import static android.media.MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START;
import static android.os.AsyncTask.Status.RUNNING;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.View.GONE;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_SHORT;
import static androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -25,6 +12,7 @@ import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaPlayer.OnInfoListener; import android.media.MediaPlayer.OnInfoListener;
import android.media.MediaPlayer.OnPreparedListener; import android.media.MediaPlayer.OnPreparedListener;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
@ -48,6 +36,7 @@ import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.adapter.ImageAdapter; import org.nuclearfog.twidda.adapter.ImageAdapter;
import org.nuclearfog.twidda.adapter.ImageAdapter.OnImageClickListener; import org.nuclearfog.twidda.adapter.ImageAdapter.OnImageClickListener;
import org.nuclearfog.twidda.backend.ImageLoader; import org.nuclearfog.twidda.backend.ImageLoader;
import org.nuclearfog.twidda.backend.SeekbarUpdater;
import org.nuclearfog.twidda.backend.engine.EngineException; import org.nuclearfog.twidda.backend.engine.EngineException;
import org.nuclearfog.twidda.backend.holder.ImageHolder; import org.nuclearfog.twidda.backend.holder.ImageHolder;
import org.nuclearfog.twidda.backend.utils.AppStyles; import org.nuclearfog.twidda.backend.utils.AppStyles;
@ -56,10 +45,18 @@ import org.nuclearfog.twidda.backend.utils.StringTools;
import org.nuclearfog.twidda.database.GlobalSettings; import org.nuclearfog.twidda.database.GlobalSettings;
import org.nuclearfog.zoomview.ZoomView; import org.nuclearfog.zoomview.ZoomView;
import java.lang.ref.WeakReference; import static android.media.MediaPlayer.MEDIA_ERROR_UNKNOWN;
import java.util.concurrent.Executors; import static android.media.MediaPlayer.MEDIA_INFO_BUFFERING_END;
import java.util.concurrent.ScheduledExecutorService; import static android.media.MediaPlayer.MEDIA_INFO_BUFFERING_START;
import java.util.concurrent.TimeUnit; import static android.media.MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START;
import static android.os.AsyncTask.Status.RUNNING;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.View.GONE;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_SHORT;
import static androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL;
/** /**
* Media viewer activity for images and videos * Media viewer activity for images and videos
@ -115,11 +112,9 @@ public class MediaViewer extends MediaActivity implements OnImageClickListener,
IDLE IDLE
} }
private WeakReference<MediaViewer> updateEvent = new WeakReference<>(this);
@Nullable
private ScheduledExecutorService progressUpdate;
@Nullable @Nullable
private ImageLoader imageAsync; private ImageLoader imageAsync;
private SeekbarUpdater seekUpdate;
private TextView duration, position; private TextView duration, position;
private ProgressBar loadingCircle; private ProgressBar loadingCircle;
@ -190,23 +185,15 @@ public class MediaViewer extends MediaActivity implements OnImageClickListener,
case MEDIAVIEWER_VIDEO: case MEDIAVIEWER_VIDEO:
controlPanel.setVisibility(VISIBLE); controlPanel.setVisibility(VISIBLE);
if (!mediaLinks[0].startsWith("http")) if (mediaLinks[0].startsWith("/"))
share.setVisibility(GONE); // local image share.setVisibility(GONE); // local image
final Runnable seekUpdate = new Runnable() { else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && mediaLinks[0].startsWith("https://")) {
public void run() { // for any reason VideoView ignores TLS 1.2 setup, so we have to use http:// instead
if (updateEvent.get() != null) { // todo find a solution to add TLS 1.2 support for pre lollipop devices
updateEvent.get().updateSeekBar(); mediaLinks[0] = "http://" + mediaLinks[0].substring(8);
} }
} seekUpdate = new SeekbarUpdater(this, PROGRESS_UPDATE);
}; // fall through
progressUpdate = Executors.newScheduledThreadPool(1);
progressUpdate.scheduleWithFixedDelay(new Runnable() {
public void run() {
if (updateEvent.get() != null) {
updateEvent.get().runOnUiThread(seekUpdate);
}
}
}, PROGRESS_UPDATE, PROGRESS_UPDATE, TimeUnit.MILLISECONDS);
case MEDIAVIEWER_ANGIF: case MEDIAVIEWER_ANGIF:
videoView.setVisibility(VISIBLE); videoView.setVisibility(VISIBLE);
videoView.setZOrderMediaOverlay(true); // disable black background videoView.setZOrderMediaOverlay(true); // disable black background
@ -243,8 +230,8 @@ public class MediaViewer extends MediaActivity implements OnImageClickListener,
protected void onDestroy() { protected void onDestroy() {
if (imageAsync != null && imageAsync.getStatus() == RUNNING) if (imageAsync != null && imageAsync.getStatus() == RUNNING)
imageAsync.cancel(true); imageAsync.cancel(true);
if (progressUpdate != null) if (seekUpdate != null)
progressUpdate.shutdown(); seekUpdate.shutdown();
super.onDestroy(); super.onDestroy();
} }
@ -480,7 +467,7 @@ public class MediaViewer extends MediaActivity implements OnImageClickListener,
/** /**
* updates controller panel SeekBar * updates controller panel SeekBar
*/ */
private void updateSeekBar() { public void updateSeekBar() {
int videoPos = video_progress.getProgress(); int videoPos = video_progress.getProgress();
switch (playStat) { switch (playStat) {
case PLAY: case PLAY:

View File

@ -14,6 +14,7 @@ import static org.nuclearfog.twidda.activities.TweetEditor.KEY_TWEETPOPUP_REPLYI
import static org.nuclearfog.twidda.activities.TweetEditor.KEY_TWEETPOPUP_TEXT; import static org.nuclearfog.twidda.activities.TweetEditor.KEY_TWEETPOPUP_TEXT;
import static org.nuclearfog.twidda.activities.UserDetail.KEY_USERDETAIL_ID; import static org.nuclearfog.twidda.activities.UserDetail.KEY_USERDETAIL_ID;
import static org.nuclearfog.twidda.activities.UserDetail.KEY_USERDETAIL_MODE; import static org.nuclearfog.twidda.activities.UserDetail.KEY_USERDETAIL_MODE;
import static org.nuclearfog.twidda.activities.UserDetail.USERLIST_FAVORIT;
import static org.nuclearfog.twidda.activities.UserDetail.USERLIST_RETWEETS; import static org.nuclearfog.twidda.activities.UserDetail.USERLIST_RETWEETS;
import static org.nuclearfog.twidda.fragments.TweetFragment.INTENT_TWEET_REMOVED_ID; import static org.nuclearfog.twidda.fragments.TweetFragment.INTENT_TWEET_REMOVED_ID;
import static org.nuclearfog.twidda.fragments.TweetFragment.INTENT_TWEET_UPDATE_DATA; import static org.nuclearfog.twidda.fragments.TweetFragment.INTENT_TWEET_UPDATE_DATA;
@ -203,6 +204,7 @@ public class TweetActivity extends AppCompatActivity implements OnClickListener,
replyName.setOnClickListener(this); replyName.setOnClickListener(this);
ansButton.setOnClickListener(this); ansButton.setOnClickListener(this);
rtwButton.setOnClickListener(this); rtwButton.setOnClickListener(this);
favButton.setOnClickListener(this);
rtwButton.setOnLongClickListener(this); rtwButton.setOnLongClickListener(this);
favButton.setOnLongClickListener(this); favButton.setOnLongClickListener(this);
profile_img.setOnClickListener(this); profile_img.setOnClickListener(this);
@ -334,6 +336,13 @@ public class TweetActivity extends AppCompatActivity implements OnClickListener,
userList.putExtra(KEY_USERDETAIL_MODE, USERLIST_RETWEETS); userList.putExtra(KEY_USERDETAIL_MODE, USERLIST_RETWEETS);
startActivity(userList); startActivity(userList);
} }
// show user favoriting this tweet
else if (v.getId() == R.id.tweet_favorite) {
Intent userList = new Intent(this, UserDetail.class);
userList.putExtra(KEY_USERDETAIL_ID, clickedTweet.getId());
userList.putExtra(KEY_USERDETAIL_MODE, USERLIST_FAVORIT);
startActivity(userList);
}
// open profile of the tweet author // open profile of the tweet author
else if (v.getId() == R.id.tweet_profile) { else if (v.getId() == R.id.tweet_profile) {
Intent profile = new Intent(getApplicationContext(), UserProfile.class); Intent profile = new Intent(getApplicationContext(), UserProfile.class);

View File

@ -15,11 +15,7 @@ import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.database.GlobalSettings; import org.nuclearfog.twidda.database.GlobalSettings;
import org.nuclearfog.twidda.fragments.UserFragment; import org.nuclearfog.twidda.fragments.UserFragment;
import static org.nuclearfog.twidda.fragments.UserFragment.KEY_FRAG_USER_ID; import static org.nuclearfog.twidda.fragments.UserFragment.*;
import static org.nuclearfog.twidda.fragments.UserFragment.KEY_FRAG_USER_MODE;
import static org.nuclearfog.twidda.fragments.UserFragment.USER_FRAG_FOLLOWS;
import static org.nuclearfog.twidda.fragments.UserFragment.USER_FRAG_FRIENDS;
import static org.nuclearfog.twidda.fragments.UserFragment.USER_FRAG_RETWEET;
/** /**
* Activity to show a list of twitter users * Activity to show a list of twitter users
@ -54,6 +50,12 @@ public class UserDetail extends AppCompatActivity {
*/ */
public static final int USERLIST_RETWEETS = 0x19F582E; public static final int USERLIST_RETWEETS = 0x19F582E;
/**
* user favoriting/liking a tweet, requires tweet ID
*/
public static final int USERLIST_FAVORIT = 0x9bcc3f99;
@Override @Override
protected void attachBaseContext(Context newBase) { protected void attachBaseContext(Context newBase) {
super.attachBaseContext(AppStyles.setFontScale(newBase)); super.attachBaseContext(AppStyles.setFontScale(newBase));
@ -72,6 +74,7 @@ public class UserDetail extends AppCompatActivity {
int mode = data.getIntExtra(KEY_USERDETAIL_MODE, 0); int mode = data.getIntExtra(KEY_USERDETAIL_MODE, 0);
long id = data.getLongExtra(KEY_USERDETAIL_ID, -1); long id = data.getLongExtra(KEY_USERDETAIL_ID, -1);
GlobalSettings settings = GlobalSettings.getInstance(this);
Bundle param = new Bundle(); Bundle param = new Bundle();
switch (mode) { switch (mode) {
@ -98,17 +101,23 @@ public class UserDetail extends AppCompatActivity {
// set toolbar title // set toolbar title
toolbar.setTitle(R.string.toolbar_userlist_retweet); toolbar.setTitle(R.string.toolbar_userlist_retweet);
break; break;
case USERLIST_FAVORIT:
// set fragment parameter
param.putLong(KEY_FRAG_USER_ID, id);
param.putInt(KEY_FRAG_USER_MODE, USER_FRAG_FAVORIT);
int title = settings.likeEnabled() ? R.string.toolbar_tweet_liker : R.string.toolbar_tweet_favoriter;
// set toolbar title
toolbar.setTitle(title);
break;
} }
// insert fragment into view // insert fragment into view
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, UserFragment.class, param, ""); fragmentTransaction.replace(R.id.fragment_container, UserFragment.class, param, "");
fragmentTransaction.commit(); fragmentTransaction.commit();
// set toolbar // set toolbar
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
// style activity // style activity
GlobalSettings settings = GlobalSettings.getInstance(this);
AppStyles.setTheme(root, settings.getBackgroundColor()); AppStyles.setTheme(root, settings.getBackgroundColor());
} }
} }

View File

@ -0,0 +1,51 @@
package org.nuclearfog.twidda.backend;
import org.nuclearfog.twidda.activities.MediaViewer;
import java.lang.ref.WeakReference;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* This class updates {@link MediaViewer}'s Seekbar while playing a video
*
* @author nuclearfog
*/
public class SeekbarUpdater implements Runnable {
private ScheduledExecutorService updater;
private WeakReference<MediaViewer> callback;
private Runnable seekUpdate = new Runnable() {
public void run() {
MediaViewer mediaViewer = callback.get();
if (mediaViewer != null) {
mediaViewer.updateSeekBar();
}
}
};
public SeekbarUpdater(MediaViewer callback, int milliseconds) {
this.callback = new WeakReference<>(callback);
updater = Executors.newScheduledThreadPool(1);
updater.scheduleWithFixedDelay(this, milliseconds, milliseconds, TimeUnit.MILLISECONDS);
}
@Override
public void run() {
MediaViewer mediaViewer = callback.get();
if (mediaViewer != null) {
mediaViewer.runOnUiThread(seekUpdate);
}
}
/**
* shutdown updater
*/
public void shutdown() {
updater.shutdown();
}
}

View File

@ -99,8 +99,7 @@ public class UserLoader extends AsyncTask<Long, Void, UserList> {
return mTwitter.getRetweeter(id, cursor); return mTwitter.getRetweeter(id, cursor);
case FAVORIT: case FAVORIT:
// TODO not implemented in Twitter4J return mTwitter.getFavoriter(id, cursor);
break;
case SEARCH: case SEARCH:
return mTwitter.searchUsers(search, cursor); return mTwitter.searchUsers(search, cursor);

View File

@ -12,36 +12,6 @@ import twitter4j.TwitterException;
*/ */
public class EngineException extends Exception { public class EngineException extends Exception {
public enum ErrorType {
RATE_LIMIT_EX,
USER_NOT_FOUND,
APP_SUSPENDED,
ACCESS_TOKEN_DEAD,
TWEET_CANT_REPLY,
RESOURCE_NOT_FOUND,
CANT_SEND_DM,
NOT_AUTHORIZED,
TWEET_TOO_LONG,
DUPLICATE_TWEET,
NO_DM_TO_USER,
DM_TOO_LONG,
TOKEN_EXPIRED,
NO_MEDIA_FOUND,
NO_LINK_DEFINED,
NO_CONNECTION,
IMAGE_NOT_LOADED,
REQUEST_CANCELLED,
ACCOUNT_UPDATE_FAILED,
ERROR_API_ACCESS_DENIED,
ERROR_NOT_DEFINED
}
enum InternalErrorType {
FILENOTFOUND,
TOKENNOTSET,
BITMAP_FAILURE
}
private final ErrorType errorType; private final ErrorType errorType;
private String msg; private String msg;
private int retryAfter; private int retryAfter;
@ -129,6 +99,8 @@ public class EngineException extends Exception {
default: default:
if (error.getStatusCode() == 401) { if (error.getStatusCode() == 401) {
errorType = ErrorType.NOT_AUTHORIZED; errorType = ErrorType.NOT_AUTHORIZED;
} else if (error.getStatusCode() == 403) {
errorType = ErrorType.REQUEST_FORBIDDEN;
} else if (error.getStatusCode() == 408) { } else if (error.getStatusCode() == 408) {
errorType = ErrorType.REQUEST_CANCELLED; errorType = ErrorType.REQUEST_CANCELLED;
} else if (error.isCausedByNetworkIssue()) { } else if (error.isCausedByNetworkIssue()) {
@ -146,7 +118,7 @@ public class EngineException extends Exception {
} }
/** /**
* Constructor for non Twitter4J errors * Constructor for app errors
* *
* @param errorCode custom error code * @param errorCode custom error code
*/ */
@ -170,6 +142,7 @@ public class EngineException extends Exception {
} }
} }
@Override @Override
public String getMessage() { public String getMessage() {
if (msg == null || msg.isEmpty()) if (msg == null || msg.isEmpty())
@ -195,7 +168,6 @@ public class EngineException extends Exception {
return errorType == RESOURCE_NOT_FOUND || errorType == USER_NOT_FOUND; return errorType == RESOURCE_NOT_FOUND || errorType == USER_NOT_FOUND;
} }
/** /**
* return time to wait after unlock access in seconds * return time to wait after unlock access in seconds
* *
@ -204,4 +176,41 @@ public class EngineException extends Exception {
public int getTimeToWait() { public int getTimeToWait() {
return retryAfter; return retryAfter;
} }
/**
* enum of error types used by this class
*/
public enum ErrorType {
RATE_LIMIT_EX,
USER_NOT_FOUND,
APP_SUSPENDED,
ACCESS_TOKEN_DEAD,
TWEET_CANT_REPLY,
RESOURCE_NOT_FOUND,
CANT_SEND_DM,
NOT_AUTHORIZED,
TWEET_TOO_LONG,
DUPLICATE_TWEET,
NO_DM_TO_USER,
DM_TOO_LONG,
TOKEN_EXPIRED,
NO_MEDIA_FOUND,
NO_LINK_DEFINED,
NO_CONNECTION,
IMAGE_NOT_LOADED,
REQUEST_CANCELLED,
REQUEST_FORBIDDEN,
ACCOUNT_UPDATE_FAILED,
ERROR_API_ACCESS_DENIED,
ERROR_NOT_DEFINED
}
/**
* error types only accessible by {@link TwitterEngine}
*/
enum InternalErrorType {
FILENOTFOUND,
TOKENNOTSET,
BITMAP_FAILURE
}
} }

View File

@ -42,6 +42,7 @@ import twitter4j.DirectMessage;
import twitter4j.DirectMessageList; import twitter4j.DirectMessageList;
import twitter4j.GeoLocation; import twitter4j.GeoLocation;
import twitter4j.IDs; import twitter4j.IDs;
import twitter4j.LikesExKt;
import twitter4j.Location; import twitter4j.Location;
import twitter4j.PagableResponseList; import twitter4j.PagableResponseList;
import twitter4j.Paging; import twitter4j.Paging;
@ -53,6 +54,8 @@ import twitter4j.Twitter;
import twitter4j.TwitterException; import twitter4j.TwitterException;
import twitter4j.TwitterFactory; import twitter4j.TwitterFactory;
import twitter4j.UploadedMedia; import twitter4j.UploadedMedia;
import twitter4j.User2;
import twitter4j.UsersResponse;
import twitter4j.auth.AccessToken; import twitter4j.auth.AccessToken;
import twitter4j.auth.RequestToken; import twitter4j.auth.RequestToken;
import twitter4j.conf.ConfigurationBuilder; import twitter4j.conf.ConfigurationBuilder;
@ -847,7 +850,7 @@ public class TwitterEngine {
* Get User who retweeted a Tweet * Get User who retweeted a Tweet
* *
* @param tweetID Tweet ID * @param tweetID Tweet ID
* @return List of users or empty list if no match * @return List of users
* @throws EngineException if Access is unavailable * @throws EngineException if Access is unavailable
*/ */
public UserList getRetweeter(long tweetID, long cursor) throws EngineException { public UserList getRetweeter(long tweetID, long cursor) throws EngineException {
@ -867,6 +870,24 @@ public class TwitterEngine {
} }
} }
/**
* get user who liked a tweet
*
* @param tweetId Tweet ID
* @return list of users liking a tweet
* @throws EngineException if Access is unavailable
*/
public UserList getFavoriter(long tweetId, long cursor) throws EngineException {
try {
UsersResponse response = LikesExKt.getLikingUsers(twitter, tweetId, null, null, null);
List<User2> users = response.getUsers();
UserList result = new UserList(cursor, 0);
result.addAll(convertUser2List(users));
return result;
} catch (TwitterException err) {
throw new EngineException(err);
}
}
/** /**
* get list of Direct Messages * get list of Direct Messages
@ -1278,7 +1299,7 @@ public class TwitterEngine {
} }
/** /**
* convert {@link twitter4j.User} to User List and filter excluded users * convert {@link twitter4j.User} list to User List and filter excluded users
* *
* @param users Twitter4J user List * @param users Twitter4J user List
* @return User * @return User
@ -1295,6 +1316,24 @@ public class TwitterEngine {
return result; return result;
} }
/**
* convert {@link User2} list to user list
*
* @param users list of user from version 2
* @return user list
*/
private List<User> convertUser2List(List<User2> users) throws TwitterException {
long id = twitter.getId();
ArrayList<User> result = new ArrayList<>();
result.ensureCapacity(users.size());
for (User2 user : users) {
if (user.getPublicMetrics() != null) {
result.add(new User(user, user.getPublicMetrics(), id));
}
}
return result;
}
/** /**
* create paging for tweets * create paging for tweets
* *

View File

@ -359,7 +359,7 @@ public class Tweet implements Serializable {
private void setTweet(Status status, long twitterId) { private void setTweet(Status status, long twitterId) {
tweetID = status.getId(); tweetID = status.getId();
time = status.getCreatedAt().getTime(); time = status.getCreatedAt().getTime();
user = new User(status.getUser(), status.getUser().getId() == twitterId); user = new User(status.getUser(), twitterId);
replyID = status.getInReplyToStatusId(); replyID = status.getInReplyToStatusId();
replyUserId = status.getInReplyToUserId(); replyUserId = status.getInReplyToUserId();
sensitiveMedia = status.isPossiblySensitive(); sensitiveMedia = status.isPossiblySensitive();

View File

@ -5,6 +5,7 @@ import androidx.annotation.NonNull;
import java.io.Serializable; import java.io.Serializable;
import twitter4j.URLEntity; import twitter4j.URLEntity;
import twitter4j.User2;
/** /**
* Container class for a twitter user * Container class for a twitter user
@ -38,19 +39,12 @@ public class User implements Serializable {
private String profileImg = ""; private String profileImg = "";
private String bannerImg = ""; private String bannerImg = "";
/**
* @param user Twitter user
* @param twitterId ID of the current user
*/
public User(twitter4j.User user, long twitterId) {
this(user, user.getId() == twitterId);
}
/** /**
* @param user Twitter user * @param user Twitter user
* @param isCurrentUser true if user is the authenticated user * @param twitterId ID of the current user
*/ */
public User(twitter4j.User user, boolean isCurrentUser) { public User(twitter4j.User user, long twitterId) {
String bannerLink = user.getProfileBannerURL(); String bannerLink = user.getProfileBannerURL();
String bio = user.getDescription(); String bio = user.getDescription();
@ -85,7 +79,16 @@ public class User implements Serializable {
favorCount = user.getFavouritesCount(); favorCount = user.getFavouritesCount();
isFollowReqSent = user.isFollowRequestSent(); isFollowReqSent = user.isFollowRequestSent();
hasDefaultImage = user.isDefaultProfileImage(); hasDefaultImage = user.isDefaultProfileImage();
this.isCurrentUser = isCurrentUser; isCurrentUser = twitterId == userID;
}
public User(User2 user, User2.PublicMetrics metrics, long id) {
this(user.getId(), user.getUsername(), user.getName(), user.getProfileImageUrl(),
user.getDescription(), user.getLocation(), id, user.getVerified(),
user.getProtected(), false, true, "", "", user.getCreatedAt().getTime(),
metrics.getFollowingCount(), metrics.getFollowersCount(),
metrics.getTweetCount(), metrics.getListedCount());
} }
/** /**
@ -95,7 +98,7 @@ public class User implements Serializable {
* @param profileImg profile image link * @param profileImg profile image link
* @param bio bio of the user * @param bio bio of the user
* @param location location name * @param location location name
* @param isCurrentUser true if this user is the authenticated user * @param currentId current ID of the user
* @param isVerified true if user is verified * @param isVerified true if user is verified
* @param isLocked true if users profile is locked * @param isLocked true if users profile is locked
* @param isFollowReqSent true if authenticated user has sent a follow request * @param isFollowReqSent true if authenticated user has sent a follow request
@ -108,7 +111,7 @@ public class User implements Serializable {
* @param tweetCount number of tweets of the user * @param tweetCount number of tweets of the user
* @param favorCount number of tweets favored by the user * @param favorCount number of tweets favored by the user
*/ */
public User(long userID, String username, String screenName, String profileImg, String bio, String location, boolean isCurrentUser, public User(long userID, String username, String screenName, String profileImg, String bio, String location, long currentId,
boolean isVerified, boolean isLocked, boolean isFollowReqSent, boolean hasDefaultImage, String link, boolean isVerified, boolean isLocked, boolean isFollowReqSent, boolean hasDefaultImage, String link,
String bannerImg, long created, int following, int follower, int tweetCount, int favorCount) { String bannerImg, long created, int following, int follower, int tweetCount, int favorCount) {
@ -127,7 +130,6 @@ public class User implements Serializable {
if (bannerImg != null) if (bannerImg != null)
this.bannerImg = bannerImg; this.bannerImg = bannerImg;
this.userID = userID; this.userID = userID;
this.isCurrentUser = isCurrentUser;
this.isVerified = isVerified; this.isVerified = isVerified;
this.isLocked = isLocked; this.isLocked = isLocked;
this.created = created; this.created = created;
@ -137,6 +139,7 @@ public class User implements Serializable {
this.favorCount = favorCount; this.favorCount = favorCount;
this.isFollowReqSent = isFollowReqSent; this.isFollowReqSent = isFollowReqSent;
this.hasDefaultImage = hasDefaultImage; this.hasDefaultImage = hasDefaultImage;
isCurrentUser = currentId == userID;
} }
/** /**

View File

@ -103,6 +103,9 @@ public final class ErrorHandler {
case REQUEST_CANCELLED: case REQUEST_CANCELLED:
return context.getString(R.string.error_result_cancelled); return context.getString(R.string.error_result_cancelled);
case REQUEST_FORBIDDEN:
return context.getString(R.string.error_forbidden_api_access);
case APP_SUSPENDED: case APP_SUSPENDED:
case ERROR_API_ACCESS_DENIED: case ERROR_API_ACCESS_DENIED:
GlobalSettings settings = GlobalSettings.getInstance(context); GlobalSettings settings = GlobalSettings.getInstance(context);

View File

@ -15,7 +15,7 @@ import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.SSLSocketFactory;
/** /**
* Enable Experimental TLS 1.2 support for devices lower than android 21 * Enable Experimental TLS 1.2 support for pre-Lollipop devices
* *
* @author fkrauthan * @author fkrauthan
* @see <a href="https://gist.githubusercontent.com/fkrauthan/ac8624466a4dee4fd02f/raw/309efc30e31c96a932ab9d19bf4d73b286b00573/TLSSocketFactory.java"/> * @see <a href="https://gist.githubusercontent.com/fkrauthan/ac8624466a4dee4fd02f/raw/309efc30e31c96a932ab9d19bf4d73b286b00573/TLSSocketFactory.java"/>
@ -62,7 +62,7 @@ public class TLSSocketFactory extends SSLSocketFactory {
* *
*/ */
TLSSocketFactory() throws KeyManagementException, NoSuchAlgorithmException { TLSSocketFactory() throws KeyManagementException, NoSuchAlgorithmException {
SSLContext context = SSLContext.getInstance("TLS"); SSLContext context = SSLContext.getInstance(TLS_1_2);
context.init(null, null, null); context.init(null, null, null);
internalSSLSocketFactory = context.getSocketFactory(); internalSSLSocketFactory = context.getSocketFactory();
} }

View File

@ -754,12 +754,11 @@ public class AppDatabase {
int tCount = cursor.getInt(cursor.getColumnIndexOrThrow(UserTable.TWEETS)); int tCount = cursor.getInt(cursor.getColumnIndexOrThrow(UserTable.TWEETS));
int fCount = cursor.getInt(cursor.getColumnIndexOrThrow(UserTable.FAVORS)); int fCount = cursor.getInt(cursor.getColumnIndexOrThrow(UserTable.FAVORS));
int userRegister = cursor.getInt(cursor.getColumnIndexOrThrow(UserRegisterTable.REGISTER)); int userRegister = cursor.getInt(cursor.getColumnIndexOrThrow(UserRegisterTable.REGISTER));
boolean isCurrentUser = homeId == userId;
boolean isVerified = (userRegister & VER_MASK) != 0; boolean isVerified = (userRegister & VER_MASK) != 0;
boolean isLocked = (userRegister & LCK_MASK) != 0; boolean isLocked = (userRegister & LCK_MASK) != 0;
boolean isReq = (userRegister & FRQ_MASK) != 0; boolean isReq = (userRegister & FRQ_MASK) != 0;
boolean defaultImg = (userRegister & DEF_IMG) != 0; boolean defaultImg = (userRegister & DEF_IMG) != 0;
return new User(userId, username, screenName, profileImg, bio, location, isCurrentUser, isVerified, return new User(userId, username, screenName, profileImg, bio, location, homeId, isVerified,
isLocked, isReq, defaultImg, link, banner, createdAt, following, follower, tCount, fCount); isLocked, isReq, defaultImg, link, banner, createdAt, following, follower, tCount, fCount);
} }

View File

@ -80,7 +80,6 @@ public class UserFragment extends ListFragment implements UserClickListener,
/** /**
* configuration to get a list of users favoring a tweet * configuration to get a list of users favoring a tweet
* todo implement this function if there is an API for it
*/ */
public static final int USER_FRAG_FAVORIT = 0xA7FB2BB4; public static final int USER_FRAG_FAVORIT = 0xA7FB2BB4;

View File

@ -223,4 +223,7 @@
<string name="info_tweet_liked">Tweet zu den Likes hinzugefügt</string> <string name="info_tweet_liked">Tweet zu den Likes hinzugefügt</string>
<string name="info_tweet_unliked">Tweet aus den Likes entfernt</string> <string name="info_tweet_unliked">Tweet aus den Likes entfernt</string>
<string name="app_info_icons">svg Icons von:</string> <string name="app_info_icons">svg Icons von:</string>
<string name="toolbar_tweet_favoriter">Tweet favorisiert von</string>
<string name="toolbar_tweet_liker">Tweet gelikt von</string>
<string name="error_forbidden_api_access">Diese API unterstützt nicht diese Aktion!</string>
</resources> </resources>

View File

@ -124,6 +124,8 @@
<string name="confirm_remove_account">remove account from list?</string> <string name="confirm_remove_account">remove account from list?</string>
<string name="account_user_id_prefix" translatable="false">User ID:</string> <string name="account_user_id_prefix" translatable="false">User ID:</string>
<string name="account_user_unnamed">\'unnamed\'</string> <string name="account_user_unnamed">\'unnamed\'</string>
<string name="toolbar_tweet_favoriter">User favoriting this tweet</string>
<string name="toolbar_tweet_liker">User liking this tweet</string>
<!-- toast messages to inform user --> <!-- toast messages to inform user -->
<string name="info_user_removed">User removed from list</string> <string name="info_user_removed">User removed from list</string>
@ -163,6 +165,7 @@
<string name="info_permission_read">Read permission needed to access images and videos.</string> <string name="info_permission_read">Read permission needed to access images and videos.</string>
<string name="info_permission_location">Location permission only needed to add location information to tweets.</string> <string name="info_permission_location">Location permission only needed to add location information to tweets.</string>
<string name="info_permission_write">Write permission used to store images.</string> <string name="info_permission_write">Write permission used to store images.</string>
<string name="info_restart_app_on_change">restarting required to apply changes</string>
<string name="info_error">Error</string> <string name="info_error">Error</string>
<!-- toast messages for error information --> <!-- toast messages for error information -->
@ -205,6 +208,7 @@
<string name="error_result_cancelled">Error, result cancelled!</string> <string name="error_result_cancelled">Error, result cancelled!</string>
<string name="error_twitter_search">Error, search query is too long or contains illegal characters!</string> <string name="error_twitter_search">Error, search query is too long or contains illegal characters!</string>
<string name="error_not_defined">Not specified error!</string> <string name="error_not_defined">Not specified error!</string>
<string name="error_forbidden_api_access">API does not support this operation!</string>
<!-- menu icon strings --> <!-- menu icon strings -->
<string name="menu_tweet">write Tweet</string> <string name="menu_tweet">write Tweet</string>
@ -246,7 +250,6 @@
<string name="dialog_button_ok">OK</string> <string name="dialog_button_ok">OK</string>
<string name="dialog_link_image_preview">Link preview image</string> <string name="dialog_link_image_preview">Link preview image</string>
<string name="dialog_link_close">close link preview</string> <string name="dialog_link_close">close link preview</string>
<string name="info_restart_app_on_change">restarting required to apply changes</string>
<string name="app_info_icons">svg icons from:</string> <string name="app_info_icons">svg icons from:</string>
<string name="app_info_icons_links" translatable="false">www.svgrepo.com www.entypo.com</string> <string name="app_info_icons_links" translatable="false">www.svgrepo.com www.entypo.com</string>