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:
parent
086234944e
commit
1d0b1daae1
|
@ -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'
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
*
|
*
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue