added multi account support, restructured database, directmessage fix, layout fix

Signed-off-by: nuclearfog <hatespirit666@gmail.com>
This commit is contained in:
nuclearfog 2021-06-09 21:38:09 +02:00
parent b7fb816d59
commit 5ffc89b578
No known key found for this signature in database
GPG Key ID: AA0271FBE406DB98
54 changed files with 1715 additions and 512 deletions

View File

@ -121,6 +121,11 @@
android:screenOrientation="portrait"
android:theme="@style/TransparencyDim" />
<activity
android:name=".activity.AccountActivity"
android:screenOrientation="portrait"
android:theme="@style/AppTheme" />
</application>
</manifest>

View File

@ -0,0 +1,108 @@
package org.nuclearfog.twidda.activity;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.FragmentTransaction;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.database.GlobalSettings;
import org.nuclearfog.twidda.fragment.AccountFragment;
import org.nuclearfog.twidda.fragment.ListFragment;
/**
* account manager activity
*
* @author nuclearfog
*/
public class AccountActivity extends AppCompatActivity {
/**
* request login page
*/
private static final int REQ_LOGIN = 0xDF14;
/**
* result code when an account was selected
*/
public static final int RET_ACCOUNT_CHANGE = 0x0456;
/**
* key to disable account selector option from menu
*/
public static final String KEY_DISABLE_SELECTOR = "disable-acc-manager";
private GlobalSettings settings;
private ListFragment fragment;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.page_fragment);
View root = findViewById(R.id.fragment_root);
Toolbar tool = findViewById(R.id.fragment_toolbar);
fragment = new AccountFragment();
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragment);
fragmentTransaction.commit();
tool.setTitle(R.string.account_page);
setSupportActionBar(tool);
settings = GlobalSettings.getInstance(this);
AppStyles.setTheme(settings, root);
}
@Override
public void onBackPressed() {
setResult(RESULT_OK);
super.onBackPressed();
}
@Override
public boolean onCreateOptionsMenu(Menu m) {
getMenuInflater().inflate(R.menu.accounts, m);
// disable account selector icon if this activity started from LoginActivity
boolean disableSelector = getIntent().getBooleanExtra(KEY_DISABLE_SELECTOR, false);
m.findItem(R.id.action_add_account).setVisible(!disableSelector);
// theme icons
AppStyles.setMenuIconColor(m, settings.getIconColor());
return super.onCreateOptionsMenu(m);
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_add_account) {
// open login page to add new account
Intent loginIntent = new Intent(this, LoginActivity.class);
startActivityForResult(loginIntent, REQ_LOGIN);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQ_LOGIN && resultCode == Activity.RESULT_OK) {
// new account registered, reload fragment
fragment.reset();
}
}
}

View File

@ -39,6 +39,7 @@ import org.nuclearfog.twidda.backend.engine.TwitterEngine;
import org.nuclearfog.twidda.backend.model.TrendLocation;
import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.backend.utils.ErrorHandler;
import org.nuclearfog.twidda.database.AccountDatabase;
import org.nuclearfog.twidda.database.DatabaseAdapter;
import org.nuclearfog.twidda.database.GlobalSettings;
import org.nuclearfog.twidda.dialog.ConfirmDialog;
@ -257,9 +258,13 @@ public class AppSettings extends AppCompatActivity implements OnClickListener, O
public void onConfirm(DialogType type) {
// confirm log out
if (type == DialogType.APP_LOG_OUT) {
settings.logout();
// reset twitter singleton
TwitterEngine.resetTwitter();
// remove account from database
// todo remove database and entry asynchronously
AccountDatabase.getInstance(this).removeLogin(settings.getCurrentUserId());
DatabaseAdapter.deleteDatabase(getApplicationContext());
settings.logout();
setResult(RETURN_APP_LOGOUT);
finish();
}

View File

@ -10,12 +10,12 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.viewpager.widget.ViewPager;
import androidx.fragment.app.FragmentTransaction;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.adapter.FragmentAdapter;
import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.database.GlobalSettings;
import org.nuclearfog.twidda.fragment.MessageFragment;
/**
* Activity for the direct message page of the current user
@ -29,19 +29,17 @@ public class DirectMessage extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle b) {
super.onCreate(b);
setContentView(R.layout.page_dm);
Toolbar tool = findViewById(R.id.dm_toolbar);
View root = findViewById(R.id.dm_layout);
ViewPager pager = findViewById(R.id.dm_pager);
setContentView(R.layout.page_fragment);
View root = findViewById(R.id.fragment_root);
Toolbar tool = findViewById(R.id.fragment_toolbar);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, new MessageFragment());
fragmentTransaction.commit();
tool.setTitle(R.string.directmessage);
setSupportActionBar(tool);
FragmentAdapter adapter = new FragmentAdapter(getSupportFragmentManager());
adapter.setupMessagePage();
pager.setOffscreenPageLimit(1);
pager.setAdapter(adapter);
settings = GlobalSettings.getInstance(this);
AppStyles.setTheme(settings, root);
}

View File

@ -30,15 +30,19 @@ import static android.os.AsyncTask.Status.FINISHED;
import static android.os.AsyncTask.Status.RUNNING;
import static android.widget.Toast.LENGTH_LONG;
import static android.widget.Toast.LENGTH_SHORT;
import static org.nuclearfog.twidda.activity.AccountActivity.KEY_DISABLE_SELECTOR;
import static org.nuclearfog.twidda.activity.AccountActivity.RET_ACCOUNT_CHANGE;
/**
* Login Activity of the App
* Account Activity of the App
* called from {@link MainActivity} when this app isn't logged in to twitter
*
* @author nuclearfog
*/
public class LoginActivity extends AppCompatActivity implements OnClickListener {
private static final int REQUEST_ACCOUNT_SELECT = 0x384F;
private Registration registerAsync;
private GlobalSettings settings;
@ -100,11 +104,24 @@ public class LoginActivity extends AppCompatActivity implements OnClickListener
if (item.getItemId() == R.id.login_setting) {
Intent settings = new Intent(this, AppSettings.class);
startActivity(settings);
} else if (item.getItemId() == R.id.login_select_account) {
Intent accountManager = new Intent(this, AccountActivity.class);
accountManager.putExtra(KEY_DISABLE_SELECTOR, true);
startActivityForResult(accountManager, REQUEST_ACCOUNT_SELECT);
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_ACCOUNT_SELECT && resultCode == RET_ACCOUNT_CHANGE) {
// account selected, return to MainActivity
onSuccess();
}
}
@Override
public void onClick(View v) {
// get login request link
@ -117,9 +134,12 @@ public class LoginActivity extends AppCompatActivity implements OnClickListener
}
// verify user
else if (v.getId() == R.id.login_verifier) {
// check if user clicked on PIN button
if (registerAsync == null || registerAsync.getStatus() != FINISHED) {
Toast.makeText(this, R.string.info_get_link, LENGTH_LONG).show();
} else if (pinInput.getText() != null && pinInput.length() > 0) {
}
// check if PIN exists
else if (pinInput.getText() != null && pinInput.length() > 0) {
Toast.makeText(this, R.string.info_login_to_twitter, LENGTH_LONG).show();
String twitterPin = pinInput.getText().toString();
registerAsync = new Registration(this);

View File

@ -161,6 +161,7 @@ public class MainActivity extends AppCompatActivity implements OnTabSelectedList
MenuItem tweet = m.findItem(R.id.action_tweet);
MenuItem search = m.findItem(R.id.action_search);
MenuItem setting = m.findItem(R.id.action_settings);
MenuItem account = m.findItem(R.id.action_account);
switch (tablayout.getSelectedTabPosition()) {
case 0:
@ -168,6 +169,7 @@ public class MainActivity extends AppCompatActivity implements OnTabSelectedList
search.setVisible(false);
tweet.setVisible(true);
setting.setVisible(false);
account.setVisible(false);
search.collapseActionView();
break;
@ -176,6 +178,7 @@ public class MainActivity extends AppCompatActivity implements OnTabSelectedList
search.setVisible(true);
tweet.setVisible(false);
setting.setVisible(true);
account.setVisible(false);
break;
case 2:
@ -183,6 +186,7 @@ public class MainActivity extends AppCompatActivity implements OnTabSelectedList
search.setVisible(false);
tweet.setVisible(false);
setting.setVisible(true);
account.setVisible(true);
search.collapseActionView();
break;
}
@ -213,6 +217,11 @@ public class MainActivity extends AppCompatActivity implements OnTabSelectedList
SearchView searchView = (SearchView) item.getActionView();
AppStyles.setTheme(settings, searchView, Color.TRANSPARENT);
}
// open account manager
else if (item.getItemId() == R.id.action_account) {
Intent accountManager = new Intent(this, AccountActivity.class);
startActivityForResult(accountManager, REQUEST_APP_LOGIN);
}
return super.onOptionsItemSelected(item);
}

View File

@ -158,10 +158,10 @@ public class MediaViewer extends MediaActivity implements OnImageClickListener,
play.setImageResource(R.drawable.play);
pause.setImageResource(R.drawable.pause);
adapter = new ImageAdapter(this);
GlobalSettings settings = GlobalSettings.getInstance(this);
AppStyles.setProgressColor(loadingCircle, settings.getHighlightColor());
AppStyles.setTheme(settings, controlPanel, settings.getCardColor());
adapter = new ImageAdapter(settings, this);
// get intent data and type
mediaLinks = getIntent().getStringArrayExtra(KEY_MEDIA_LINK);

View File

@ -23,7 +23,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.viewpager.widget.ViewPager;
import androidx.fragment.app.FragmentTransaction;
import com.squareup.picasso.Picasso;
@ -31,7 +31,6 @@ import org.nuclearfog.tag.Tagger;
import org.nuclearfog.tag.Tagger.OnTagClickListener;
import org.nuclearfog.textviewtool.LinkAndScrollMovement;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.adapter.FragmentAdapter;
import org.nuclearfog.twidda.backend.TweetAction;
import org.nuclearfog.twidda.backend.TweetAction.Action;
import org.nuclearfog.twidda.backend.engine.EngineException;
@ -44,6 +43,7 @@ import org.nuclearfog.twidda.dialog.ConfirmDialog;
import org.nuclearfog.twidda.dialog.ConfirmDialog.DialogType;
import org.nuclearfog.twidda.dialog.ConfirmDialog.OnConfirmListener;
import org.nuclearfog.twidda.dialog.LinkDialog;
import org.nuclearfog.twidda.fragment.TweetFragment;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
@ -68,8 +68,12 @@ import static org.nuclearfog.twidda.activity.UserDetail.KEY_USERDETAIL_MODE;
import static org.nuclearfog.twidda.activity.UserDetail.USERLIST_RETWEETS;
import static org.nuclearfog.twidda.fragment.TweetFragment.INTENT_TWEET_REMOVED_ID;
import static org.nuclearfog.twidda.fragment.TweetFragment.INTENT_TWEET_UPDATE_DATA;
import static org.nuclearfog.twidda.fragment.TweetFragment.KEY_FRAG_TWEET_ID;
import static org.nuclearfog.twidda.fragment.TweetFragment.KEY_FRAG_TWEET_MODE;
import static org.nuclearfog.twidda.fragment.TweetFragment.KEY_FRAG_TWEET_SEARCH;
import static org.nuclearfog.twidda.fragment.TweetFragment.RETURN_TWEET_NOT_FOUND;
import static org.nuclearfog.twidda.fragment.TweetFragment.RETURN_TWEET_UPDATE;
import static org.nuclearfog.twidda.fragment.TweetFragment.TWEET_FRAG_ANSWER;
/**
* Tweet Activity for tweet and user information
@ -118,7 +122,6 @@ public class TweetActivity extends AppCompatActivity implements OnClickListener,
super.onCreate(b);
setContentView(R.layout.page_tweet);
View root = findViewById(R.id.tweet_layout);
ViewPager pager = findViewById(R.id.tweet_pager);
toolbar = findViewById(R.id.tweet_toolbar);
ansButton = findViewById(R.id.tweet_answer);
rtwButton = findViewById(R.id.tweet_retweet);
@ -136,6 +139,7 @@ public class TweetActivity extends AppCompatActivity implements OnClickListener,
sensitive_media = findViewById(R.id.tweet_sensitive);
retweeter = findViewById(R.id.tweet_retweeter_reference);
// get parameter
Object data = getIntent().getSerializableExtra(KEY_TWEET_DATA);
long tweetId;
String username;
@ -153,10 +157,17 @@ public class TweetActivity extends AppCompatActivity implements OnClickListener,
username = getIntent().getStringExtra(KEY_TWEET_NAME);
tweetId = getIntent().getLongExtra(KEY_TWEET_ID, -1);
}
FragmentAdapter adapter = new FragmentAdapter(getSupportFragmentManager());
adapter.setupTweetPage(tweetId, username);
pager.setOffscreenPageLimit(1);
pager.setAdapter(adapter);
// create list fragment for tweet replies
Bundle param = new Bundle();
param.putInt(KEY_FRAG_TWEET_MODE, TWEET_FRAG_ANSWER);
param.putString(KEY_FRAG_TWEET_SEARCH, username);
param.putLong(KEY_FRAG_TWEET_ID, tweetId);
// insert fragment into view
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.tweet_reply_fragment, TweetFragment.class, param);
fragmentTransaction.commit();
settings = GlobalSettings.getInstance(this);
ansButton.setCompoundDrawablesWithIntrinsicBounds(R.drawable.answer, 0, 0, 0);
@ -168,12 +179,14 @@ public class TweetActivity extends AppCompatActivity implements OnClickListener,
retweeter.setCompoundDrawablesWithIntrinsicBounds(R.drawable.retweet, 0, 0, 0);
tweetText.setMovementMethod(LinkAndScrollMovement.getInstance());
tweetText.setLinkTextColor(settings.getHighlightColor());
deleteDialog = new ConfirmDialog(this, DialogType.TWEET_DELETE, this);
linkPreview = new LinkDialog(this);
toolbar.setTitle("");
setSupportActionBar(toolbar);
AppStyles.setTheme(settings, root);
deleteDialog = new ConfirmDialog(this, DialogType.TWEET_DELETE, this);
linkPreview = new LinkDialog(this);
retweeter.setOnClickListener(this);
replyName.setOnClickListener(this);
ansButton.setOnClickListener(this);

View File

@ -7,12 +7,18 @@ import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.viewpager.widget.ViewPager;
import androidx.fragment.app.FragmentTransaction;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.adapter.FragmentAdapter;
import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.database.GlobalSettings;
import org.nuclearfog.twidda.fragment.UserFragment;
import static org.nuclearfog.twidda.fragment.UserFragment.KEY_FRAG_USER_ID;
import static org.nuclearfog.twidda.fragment.UserFragment.KEY_FRAG_USER_MODE;
import static org.nuclearfog.twidda.fragment.UserFragment.USER_FRAG_FOLLOWS;
import static org.nuclearfog.twidda.fragment.UserFragment.USER_FRAG_FRIENDS;
import static org.nuclearfog.twidda.fragment.UserFragment.USER_FRAG_RETWEET;
/**
* Activity to show a list of twitter users
@ -51,34 +57,52 @@ public class UserDetail extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle b) {
super.onCreate(b);
setContentView(R.layout.page_userlist);
View root = findViewById(R.id.user_view);
Toolbar toolbar = findViewById(R.id.user_toolbar);
ViewPager pager = findViewById(R.id.user_pager);
GlobalSettings settings = GlobalSettings.getInstance(this);
FragmentAdapter adapter = new FragmentAdapter(getSupportFragmentManager());
pager.setAdapter(adapter);
setContentView(R.layout.page_fragment);
View root = findViewById(R.id.fragment_root);
Toolbar toolbar = findViewById(R.id.fragment_toolbar);
// get parameter
Intent data = getIntent();
int mode = data.getIntExtra(KEY_USERDETAIL_MODE, 0);
long id = data.getLongExtra(KEY_USERDETAIL_ID, -1);
switch (data.getIntExtra(KEY_USERDETAIL_MODE, 0)) {
Bundle param = new Bundle();
switch (mode) {
case USERLIST_FRIENDS:
// set fragment parameter
param.putLong(KEY_FRAG_USER_ID, id);
param.putInt(KEY_FRAG_USER_MODE, USER_FRAG_FRIENDS);
// set toolbar title
toolbar.setTitle(R.string.userlist_following);
adapter.setupFriendsPage(id);
break;
case USERLIST_FOLLOWER:
// set fragment parameter
param.putLong(KEY_FRAG_USER_ID, id);
param.putInt(KEY_FRAG_USER_MODE, USER_FRAG_FOLLOWS);
// set toolbar title
toolbar.setTitle(R.string.userlist_follower);
adapter.setupFollowerPage(id);
break;
case USERLIST_RETWEETS:
// set fragment parameter
param.putLong(KEY_FRAG_USER_ID, id);
param.putInt(KEY_FRAG_USER_MODE, USER_FRAG_RETWEET);
// set toolbar title
toolbar.setTitle(R.string.toolbar_userlist_retweet);
adapter.setupRetweeterPage(id);
break;
}
// insert fragment into view
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, UserFragment.class, param, "");
fragmentTransaction.commit();
// set toolbar
setSupportActionBar(toolbar);
// style activity
GlobalSettings settings = GlobalSettings.getInstance(this);
AppStyles.setTheme(settings, root);
}
}

View File

@ -0,0 +1,136 @@
package org.nuclearfog.twidda.adapter;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView.Adapter;
import com.squareup.picasso.Picasso;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.adapter.holder.LoginHolder;
import org.nuclearfog.twidda.backend.model.Account;
import org.nuclearfog.twidda.backend.model.User;
import org.nuclearfog.twidda.backend.utils.StringTools;
import org.nuclearfog.twidda.database.GlobalSettings;
import org.nuclearfog.twidda.fragment.AccountFragment;
import java.util.ArrayList;
import java.util.List;
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
/**
* adapter for {@link AccountFragment}
*
* @author nuclearfog
*/
public class AccountAdapter extends Adapter<LoginHolder> {
private List<Account> data = new ArrayList<>();
private GlobalSettings settings;
private OnLoginClickListener listener;
/**
*
*/
public AccountAdapter(GlobalSettings settings, OnLoginClickListener l) {
this.settings = settings;
this.listener = l;
}
/**
* sets login data
*
* @param data list with login items
*/
public void setData(List<Account> data) {
this.data.clear();
this.data.addAll(data);
notifyDataSetChanged();
}
@NonNull
@Override
public LoginHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
final LoginHolder holder = new LoginHolder(parent, settings);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = holder.getLayoutPosition();
if (position != NO_POSITION) {
Account account = data.get(position);
listener.onLoginClick(account);
}
}
});
holder.remove.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = holder.getLayoutPosition();
if (position != NO_POSITION) {
Account account = data.get(position);
listener.onDeleteClick(account);
}
}
});
return holder;
}
@Override
public void onBindViewHolder(@NonNull LoginHolder holder, int position) {
Account account = data.get(position);
User user = account.getUser();
String date = StringTools.formatCreationTime(account.getLoginDate());
holder.date.setText(date);
if (user != null) {
holder.username.setText(user.getUsername());
holder.screenname.setText(user.getScreenname());
String pbLink = user.getImageLink();
if (!user.hasDefaultProfileImage()) {
pbLink += settings.getImageSuffix();
}
Picasso.get().load(pbLink).transform(new RoundedCornersTransformation(2, 0))
.error(R.drawable.no_image).into(holder.profile);
}
}
@Override
public int getItemCount() {
return data.size();
}
@MainThread
public void clear() {
data.clear();
notifyDataSetChanged();
}
/**
* click listener for an item
*/
public interface OnLoginClickListener {
/**
* called on item select
*
* @param account selected account information
*/
void onLoginClick(Account account);
/**
* called to remove item
*
* @param account selected account information
*/
void onDeleteClick(Account account);
}
}

View File

@ -8,7 +8,6 @@ import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import org.nuclearfog.twidda.fragment.ListFragment;
import org.nuclearfog.twidda.fragment.MessageFragment;
import org.nuclearfog.twidda.fragment.TrendFragment;
import org.nuclearfog.twidda.fragment.TweetFragment;
import org.nuclearfog.twidda.fragment.UserFragment;
@ -17,7 +16,6 @@ import org.nuclearfog.twidda.fragment.UserListFragment;
import static org.nuclearfog.twidda.fragment.TweetFragment.KEY_FRAG_TWEET_ID;
import static org.nuclearfog.twidda.fragment.TweetFragment.KEY_FRAG_TWEET_MODE;
import static org.nuclearfog.twidda.fragment.TweetFragment.KEY_FRAG_TWEET_SEARCH;
import static org.nuclearfog.twidda.fragment.TweetFragment.TWEET_FRAG_ANSWER;
import static org.nuclearfog.twidda.fragment.TweetFragment.TWEET_FRAG_FAVORS;
import static org.nuclearfog.twidda.fragment.TweetFragment.TWEET_FRAG_HOME;
import static org.nuclearfog.twidda.fragment.TweetFragment.TWEET_FRAG_LIST;
@ -28,10 +26,7 @@ import static org.nuclearfog.twidda.fragment.UserFragment.KEY_FRAG_DEL_USER;
import static org.nuclearfog.twidda.fragment.UserFragment.KEY_FRAG_USER_ID;
import static org.nuclearfog.twidda.fragment.UserFragment.KEY_FRAG_USER_MODE;
import static org.nuclearfog.twidda.fragment.UserFragment.KEY_FRAG_USER_SEARCH;
import static org.nuclearfog.twidda.fragment.UserFragment.USER_FRAG_FOLLOWS;
import static org.nuclearfog.twidda.fragment.UserFragment.USER_FRAG_FRIENDS;
import static org.nuclearfog.twidda.fragment.UserFragment.USER_FRAG_LISTS;
import static org.nuclearfog.twidda.fragment.UserFragment.USER_FRAG_RETWEET;
import static org.nuclearfog.twidda.fragment.UserFragment.USER_FRAG_SEARCH;
import static org.nuclearfog.twidda.fragment.UserFragment.USER_FRAG_SUBSCR;
import static org.nuclearfog.twidda.fragment.UserListFragment.KEY_FRAG_LIST_LIST_TYPE;
@ -149,77 +144,6 @@ public class FragmentAdapter extends FragmentStatePagerAdapter {
notifyDataSetChanged();
}
/**
* setup adapter for tweet page to show replies of a tweet
*
* @param tweetId ID of the tweet
* @param replyName screen name of the author, needed to search for answers
*/
public void setupTweetPage(long tweetId, String replyName) {
Bundle param = new Bundle();
param.putInt(KEY_FRAG_TWEET_MODE, TWEET_FRAG_ANSWER);
param.putString(KEY_FRAG_TWEET_SEARCH, replyName);
param.putLong(KEY_FRAG_TWEET_ID, tweetId);
fragments = new ListFragment[1];
fragments[0] = new TweetFragment();
fragments[0].setArguments(param);
notifyDataSetChanged();
}
/**
* setup adapter for a list of friends
*
* @param userId ID of the user
*/
public void setupFriendsPage(long userId) {
Bundle param = new Bundle();
param.putLong(KEY_FRAG_USER_ID, userId);
param.putInt(KEY_FRAG_USER_MODE, USER_FRAG_FRIENDS);
fragments = new ListFragment[1];
fragments[0] = new UserFragment();
fragments[0].setArguments(param);
notifyDataSetChanged();
}
/**
* setup adapter for a list of follower
*
* @param userId ID of the user
*/
public void setupFollowerPage(long userId) {
Bundle param = new Bundle();
param.putLong(KEY_FRAG_USER_ID, userId);
param.putInt(KEY_FRAG_USER_MODE, USER_FRAG_FOLLOWS);
fragments = new ListFragment[1];
fragments[0] = new UserFragment();
fragments[0].setArguments(param);
notifyDataSetChanged();
}
/**
* setup adapter for a list of direct messages
*/
public void setupMessagePage() {
fragments = new ListFragment[1];
fragments[0] = new MessageFragment();
notifyDataSetChanged();
}
/**
* setup adapter for a list of users which retweets a tweet
*
* @param tweetId ID if the tweet
*/
public void setupRetweeterPage(long tweetId) {
Bundle param = new Bundle();
param.putLong(KEY_FRAG_USER_ID, tweetId);
param.putInt(KEY_FRAG_USER_MODE, USER_FRAG_RETWEET);
fragments = new ListFragment[1];
fragments[0] = new UserFragment();
fragments[0].setArguments(param);
notifyDataSetChanged();
}
/**
* setup adapter for a list of user lists created by an user
*

View File

@ -13,6 +13,7 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import org.nuclearfog.twidda.adapter.holder.Footer;
import org.nuclearfog.twidda.adapter.holder.ImageItem;
import org.nuclearfog.twidda.backend.holder.ImageHolder;
import org.nuclearfog.twidda.database.GlobalSettings;
import java.util.LinkedList;
import java.util.List;
@ -39,6 +40,8 @@ public class ImageAdapter extends Adapter<ViewHolder> {
private OnImageClickListener itemClickListener;
private GlobalSettings settings;
private List<ImageHolder> images = new LinkedList<>();
private boolean loading = false;
private boolean saveImg = true;
@ -46,8 +49,9 @@ public class ImageAdapter extends Adapter<ViewHolder> {
/**
* @param itemClickListener click listener
*/
public ImageAdapter(OnImageClickListener itemClickListener) {
public ImageAdapter(GlobalSettings settings, OnImageClickListener itemClickListener) {
this.itemClickListener = itemClickListener;
this.settings = settings;
}
/**
@ -111,7 +115,7 @@ public class ImageAdapter extends Adapter<ViewHolder> {
@Override
public ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, int viewType) {
if (viewType == PICTURE) {
final ImageItem item = new ImageItem(parent);
final ImageItem item = new ImageItem(parent, settings);
item.preview.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
@ -137,7 +141,7 @@ public class ImageAdapter extends Adapter<ViewHolder> {
}
return item;
} else {
return new Footer(parent, true);
return new Footer(parent, settings, true);
}
}
@ -157,16 +161,16 @@ public class ImageAdapter extends Adapter<ViewHolder> {
public interface OnImageClickListener {
/**
* simple click on image_add
* called to select an image
*
* @param image selected image_add bitmap
* @param image selected image bitmap
*/
void onImageClick(Bitmap image);
/**
* long touch on image_add
* called to save image to storage
*
* @param image selected image_add bitmap
* @param image selected image bitmap
* @param index current image index
*/
void onImageSave(Bitmap image, int index);

View File

@ -153,7 +153,7 @@ public class ListAdapter extends Adapter<ViewHolder> {
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == ITEM_LIST) {
final ListHolder itemHolder = new ListHolder(parent);
final ListHolder itemHolder = new ListHolder(parent, settings);
itemHolder.profile_img.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
@ -178,7 +178,7 @@ public class ListAdapter extends Adapter<ViewHolder> {
});
return itemHolder;
} else {
final Footer footer = new Footer(parent, false);
final Footer footer = new Footer(parent, settings, false);
footer.loadBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {

View File

@ -140,7 +140,7 @@ public class MessageAdapter extends Adapter<ViewHolder> {
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == TYPE_MESSAGE) {
final MessageHolder vh = new MessageHolder(parent);
final MessageHolder vh = new MessageHolder(parent, settings);
vh.buttons[0].setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@ -170,7 +170,7 @@ public class MessageAdapter extends Adapter<ViewHolder> {
});
return vh;
} else {
final Footer footer = new Footer(parent, false);
final Footer footer = new Footer(parent, settings, false);
footer.loadBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

View File

@ -13,6 +13,7 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.adapter.holder.TrendHolder;
import org.nuclearfog.twidda.backend.model.Trend;
import org.nuclearfog.twidda.database.GlobalSettings;
import java.text.NumberFormat;
import java.util.ArrayList;
@ -42,12 +43,14 @@ public class TrendAdapter extends Adapter<ViewHolder> {
private TrendClickListener itemClickListener;
private GlobalSettings settings;
private List<Trend> trends = new ArrayList<>(INIT_COUNT);
/**
* @param itemClickListener Listener for item click
*/
public TrendAdapter(TrendClickListener itemClickListener) {
public TrendAdapter(GlobalSettings settings, TrendClickListener itemClickListener) {
this.settings = settings;
this.itemClickListener = itemClickListener;
}
@ -91,7 +94,7 @@ public class TrendAdapter extends Adapter<ViewHolder> {
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
final TrendHolder vh = new TrendHolder(parent);
final TrendHolder vh = new TrendHolder(parent, settings);
vh.itemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {

View File

@ -218,7 +218,7 @@ public class TweetAdapter extends Adapter<ViewHolder> {
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == VIEW_TWEET) {
final TweetHolder vh = new TweetHolder(parent);
final TweetHolder vh = new TweetHolder(parent, settings);
vh.itemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
@ -231,7 +231,7 @@ public class TweetAdapter extends Adapter<ViewHolder> {
});
return vh;
} else {
final Footer footer = new Footer(parent, false);
final Footer footer = new Footer(parent, settings, false);
footer.loadBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {

View File

@ -160,7 +160,7 @@ public class UserAdapter extends Adapter<ViewHolder> {
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == ITEM_USER) {
final UserHolder vh = new UserHolder(parent);
final UserHolder vh = new UserHolder(parent, settings);
vh.itemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
@ -188,7 +188,7 @@ public class UserAdapter extends Adapter<ViewHolder> {
}
return vh;
} else {
final Footer footer = new Footer(parent, false);
final Footer footer = new Footer(parent, settings, false);
footer.loadBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {

View File

@ -31,19 +31,19 @@ public class Footer extends ViewHolder {
* @param parent Parent view from adapter
* @param horizontal true if footer orientation is horizontal
*/
public Footer(ViewGroup parent, boolean horizontal) {
public Footer(ViewGroup parent, GlobalSettings settings, boolean horizontal) {
super(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_placeholder, parent, false));
// get views
CardView background = (CardView) itemView;
loadCircle = itemView.findViewById(R.id.placeholder_loading);
loadBtn = itemView.findViewById(R.id.placeholder_button);
GlobalSettings settings = GlobalSettings.getInstance(parent.getContext());
// theme views
background.setCardBackgroundColor(settings.getCardColor());
loadBtn.setTextColor(settings.getFontColor());
loadBtn.setTypeface(settings.getTypeFace());
AppStyles.setButtonColor(loadBtn, settings.getFontColor());
AppStyles.setProgressColor(loadCircle, settings.getHighlightColor());
// enable extra views
if (horizontal) {
loadBtn.setVisibility(INVISIBLE);
loadCircle.setVisibility(VISIBLE);
@ -52,6 +52,11 @@ public class Footer extends ViewHolder {
}
}
/**
* enable or disable progress circle
*
* @param enable true to enable progress, false to disable
*/
public void setLoading(boolean enable) {
if (enable) {
loadCircle.setVisibility(VISIBLE);

View File

@ -26,14 +26,15 @@ public class ImageItem extends ViewHolder {
/**
* @param parent Parent view from adapter
*/
public ImageItem(ViewGroup parent) {
public ImageItem(ViewGroup parent, GlobalSettings settings) {
super(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_image, parent, false));
// get views
CardView cardBackground = (CardView) itemView;
preview = itemView.findViewById(R.id.item_image_preview);
saveButton = itemView.findViewById(R.id.item_image_save);
// set icon
saveButton.setImageResource(R.drawable.save);
GlobalSettings settings = GlobalSettings.getInstance(parent.getContext());
// theme views
cardBackground.setCardBackgroundColor(settings.getCardColor());
AppStyles.setButtonColor(saveButton, settings.getFontColor());
AppStyles.setDrawableColor(saveButton, settings.getIconColor());

View File

@ -28,8 +28,9 @@ public class ListHolder extends ViewHolder {
/**
* @param parent Parent view from adapter
*/
public ListHolder(ViewGroup parent) {
public ListHolder(ViewGroup parent, GlobalSettings settings) {
super(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list, parent, false));
// get views
CardView background = (CardView) itemView;
profile_img = itemView.findViewById(R.id.list_owner_profile);
icons[0] = itemView.findViewById(R.id.list_user_verified);
@ -47,7 +48,7 @@ public class ListHolder extends ViewHolder {
textViews[5] = itemView.findViewById(R.id.list_member);
textViews[6] = itemView.findViewById(R.id.list_subscriber);
textViews[7] = itemView.findViewById(R.id.list_action);
// set icons
icons[0].setImageResource(R.drawable.verify);
icons[1].setImageResource(R.drawable.lock);
icons[2].setImageResource(R.drawable.user);
@ -55,8 +56,7 @@ public class ListHolder extends ViewHolder {
icons[4].setImageResource(R.drawable.calendar);
icons[5].setImageResource(R.drawable.lock);
icons[6].setImageResource(R.drawable.followback);
GlobalSettings settings = GlobalSettings.getInstance(parent.getContext());
// theme views
for (TextView tv : textViews) {
tv.setTextColor(settings.getFontColor());
tv.setTypeface(settings.getTypeFace());

View File

@ -0,0 +1,53 @@
package org.nuclearfog.twidda.adapter.holder;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.database.GlobalSettings;
import static android.graphics.PorterDuff.Mode.SRC_IN;
/**
* item holder for a user login item
*
* @author nuclearfog
*/
public class LoginHolder extends ViewHolder {
public final ImageView profile;
public final TextView username, screenname, date;
public final ImageButton remove;
/**
*
*/
public LoginHolder(ViewGroup parent, GlobalSettings settings) {
super(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_login, parent, false));
// get views
CardView background = (CardView) itemView;
username = itemView.findViewById(R.id.item_login_username);
screenname = itemView.findViewById(R.id.item_login_screenname);
date = itemView.findViewById(R.id.item_login_createdAt);
remove = itemView.findViewById(R.id.item_login_remove);
profile = itemView.findViewById(R.id.item_login_image);
// theme views
screenname.setTextColor(settings.getFontColor());
screenname.setTypeface(settings.getTypeFace());
screenname.setTextColor(settings.getFontColor());
screenname.setTypeface(settings.getTypeFace());
date.setTextColor(settings.getFontColor());
date.setTypeface(settings.getTypeFace());
remove.setImageResource(R.drawable.cross);
remove.setColorFilter(settings.getIconColor(), SRC_IN);
background.setCardBackgroundColor(settings.getCardColor());
AppStyles.setButtonColor(remove, settings.getFontColor());
}
}

View File

@ -31,8 +31,9 @@ public class MessageHolder extends ViewHolder {
/**
* @param parent Parent view from adapter
*/
public MessageHolder(ViewGroup parent) {
public MessageHolder(ViewGroup parent, GlobalSettings settings) {
super(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_dm, parent, false));
// get views
CardView background = (CardView) itemView;
ImageView receiver_icon = itemView.findViewById(R.id.dm_receiver_icon);
profile_img = itemView.findViewById(R.id.dm_profile_img);
@ -45,12 +46,11 @@ public class MessageHolder extends ViewHolder {
textViews[4] = itemView.findViewById(R.id.dm_message);
buttons[0] = itemView.findViewById(R.id.dm_answer);
buttons[1] = itemView.findViewById(R.id.dm_delete);
// set icons
receiver_icon.setImageResource(R.drawable.right);
verifiedIcon.setImageResource(R.drawable.verify);
lockedIcon.setImageResource(R.drawable.lock);
GlobalSettings settings = GlobalSettings.getInstance(parent.getContext());
// theme views
for (TextView tv : textViews) {
tv.setTextColor(settings.getFontColor());
tv.setTypeface(settings.getTypeFace());
@ -64,6 +64,7 @@ public class MessageHolder extends ViewHolder {
lockedIcon.setColorFilter(settings.getIconColor(), SRC_IN);
receiver_icon.setColorFilter(settings.getIconColor(), SRC_IN);
background.setCardBackgroundColor(settings.getCardColor());
// make links clickable
textViews[4].setMovementMethod(LinkMovementMethod.getInstance());
}
}

View File

@ -23,14 +23,14 @@ public class TrendHolder extends ViewHolder {
/**
* @param parent Parent view from adapter
*/
public TrendHolder(ViewGroup parent) {
public TrendHolder(ViewGroup parent, GlobalSettings settings) {
super(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_trend, parent, false));
// get views
CardView background = (CardView) itemView;
textViews[0] = itemView.findViewById(R.id.trendpos);
textViews[1] = itemView.findViewById(R.id.trendname);
textViews[2] = itemView.findViewById(R.id.trendvol);
GlobalSettings settings = GlobalSettings.getInstance(parent.getContext());
// theme views
background.setCardBackgroundColor(settings.getCardColor());
for (TextView tv : textViews) {
tv.setTextColor(settings.getFontColor());

View File

@ -27,8 +27,9 @@ public class TweetHolder extends ViewHolder {
/**
* @param parent Parent view from adapter
*/
public TweetHolder(ViewGroup parent) {
public TweetHolder(ViewGroup parent, GlobalSettings settings) {
super(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_tweet, parent, false));
// get views
CardView background = (CardView) itemView;
profile = itemView.findViewById(R.id.tweetPb);
verifiedIcon = itemView.findViewById(R.id.verified_icon);
@ -43,14 +44,13 @@ public class TweetHolder extends ViewHolder {
textViews[4] = itemView.findViewById(R.id.favorite_number);
textViews[5] = itemView.findViewById(R.id.retweeter);
textViews[6] = itemView.findViewById(R.id.time);
// set icons
verifiedIcon.setImageResource(R.drawable.verify);
lockedIcon.setImageResource(R.drawable.lock);
rtUser.setImageResource(R.drawable.retweet);
rtIcon.setImageResource(R.drawable.retweet);
favIcon.setImageResource(R.drawable.favorite);
GlobalSettings settings = GlobalSettings.getInstance(parent.getContext());
// theme views
verifiedIcon.setColorFilter(settings.getIconColor(), SRC_IN);
lockedIcon.setColorFilter(settings.getIconColor(), SRC_IN);
rtUser.setColorFilter(settings.getIconColor(), SRC_IN);

View File

@ -30,8 +30,9 @@ public class UserHolder extends ViewHolder {
/**
* @param parent Parent view from adapter
*/
public UserHolder(ViewGroup parent) {
public UserHolder(ViewGroup parent, GlobalSettings settings) {
super(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_user, parent, false));
// get views
CardView background = (CardView) itemView;
ImageView followingIcon = itemView.findViewById(R.id.following_icon);
ImageView followerIcon = itemView.findViewById(R.id.follower_icon);
@ -43,14 +44,13 @@ public class UserHolder extends ViewHolder {
verifyIcon = itemView.findViewById(R.id.useritem_verified);
lockedIcon = itemView.findViewById(R.id.useritem_locked);
delete = itemView.findViewById(R.id.useritem_del_user);
// set view icons
followerIcon.setImageResource(R.drawable.follower);
followingIcon.setImageResource(R.drawable.following);
verifyIcon.setImageResource(R.drawable.verify);
lockedIcon.setImageResource(R.drawable.lock);
delete.setImageResource(R.drawable.cross);
GlobalSettings settings = GlobalSettings.getInstance(parent.getContext());
// theme views
background.setCardBackgroundColor(settings.getCardColor());
followerIcon.setColorFilter(settings.getIconColor(), SRC_IN);
followingIcon.setColorFilter(settings.getIconColor(), SRC_IN);

View File

@ -0,0 +1,84 @@
package org.nuclearfog.twidda.backend;
import android.os.AsyncTask;
import androidx.annotation.Nullable;
import org.nuclearfog.twidda.backend.engine.EngineException;
import org.nuclearfog.twidda.backend.engine.TwitterEngine;
import org.nuclearfog.twidda.backend.model.Account;
import org.nuclearfog.twidda.backend.model.User;
import org.nuclearfog.twidda.database.AccountDatabase;
import org.nuclearfog.twidda.fragment.AccountFragment;
import java.lang.ref.WeakReference;
import java.util.List;
/**
* backend loader to get user information about user logins
*
* @author nuclearfog
*/
public class LoginLoader extends AsyncTask<Account, Void, List<Account>> {
@Nullable
private EngineException err;
private AccountDatabase database;
private TwitterEngine mTwitter;
private WeakReference<AccountFragment> callback;
/**
*
*/
public LoginLoader(AccountFragment fragment) {
super();
callback = new WeakReference<>(fragment);
database = AccountDatabase.getInstance(fragment.requireContext());
mTwitter = TwitterEngine.getInstance(fragment.requireContext());
}
@Override
protected List<Account> doInBackground(Account... param) {
try {
// remove account if parameter is set
if (param != null && param.length > 0) {
database.removeLogin(param[0].getId());
}
// get registered users
List<Account> result = database.getLogins();
// download user information
if (!result.isEmpty()) {
// get all user IDs
long[] ids = new long[result.size()];
for (int i = 0; i < ids.length; i++) {
ids[i] = result.get(i).getId();
}
// get user information
List<User> users = mTwitter.getUsers(ids);
for (int i = 0; i < users.size(); i++) {
result.get(i).attachUser(users.get(i));
}
}
return result;
} catch (EngineException err) {
this.err = err;
} catch (Exception err) {
err.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(List<Account> accounts) {
AccountFragment fragment = callback.get();
if (fragment != null) {
if (accounts != null) {
fragment.onSuccess(accounts);
} else {
fragment.onError(err);
}
}
}
}

View File

@ -7,6 +7,8 @@ import androidx.annotation.Nullable;
import org.nuclearfog.twidda.activity.LoginActivity;
import org.nuclearfog.twidda.backend.engine.EngineException;
import org.nuclearfog.twidda.backend.engine.TwitterEngine;
import org.nuclearfog.twidda.database.AccountDatabase;
import org.nuclearfog.twidda.database.GlobalSettings;
import java.lang.ref.WeakReference;
@ -21,25 +23,36 @@ public class Registration extends AsyncTask<String, Void, String> {
@Nullable
private EngineException twException;
private WeakReference<LoginActivity> callback;
private AccountDatabase accountDB;
private TwitterEngine mTwitter;
private GlobalSettings settings;
/**
* Login to twitter with PIN
* Account to twitter with PIN
*
* @param callback Activity Context
* @param activity Activity Context
*/
public Registration(LoginActivity callback) {
public Registration(LoginActivity activity) {
super();
this.callback = new WeakReference<>(callback);
mTwitter = TwitterEngine.getInstance(callback);
this.callback = new WeakReference<>(activity);
mTwitter = TwitterEngine.getInstance(activity);
accountDB = AccountDatabase.getInstance(activity);
settings = GlobalSettings.getInstance(activity);
}
@Override
protected String doInBackground(String... param) {
try {
// check if we need to backup current session
if (settings.isLoggedIn() && !accountDB.exists(settings.getCurrentUserId())) {
String[] tokens = settings.getCurrentUserAccessToken();
accountDB.setLogin(settings.getCurrentUserId(), tokens[0], tokens[1]);
}
// no PIN means we need to request a token to login
if (param.length == 0)
return mTwitter.request();
// login with pin
mTwitter.initialize(param[0]);
return "";
} catch (EngineException twException) {

View File

@ -19,6 +19,7 @@ import org.nuclearfog.twidda.backend.model.TrendLocation;
import org.nuclearfog.twidda.backend.model.Tweet;
import org.nuclearfog.twidda.backend.model.TwitterList;
import org.nuclearfog.twidda.backend.model.User;
import org.nuclearfog.twidda.database.AccountDatabase;
import org.nuclearfog.twidda.database.GlobalSettings;
import java.io.File;
@ -27,6 +28,8 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@ -58,14 +61,15 @@ import twitter4j.conf.ConfigurationBuilder;
@Obfuscate
public class TwitterEngine {
private GlobalSettings settings;
private static final TwitterEngine mTwitter = new TwitterEngine();
private Twitter twitter;
@Nullable
private RequestToken reqToken;
@Nullable
private AccessToken aToken;
private GlobalSettings settings;
private AccountDatabase accountDB;
private Twitter twitter;
private boolean isInitialized = false;
@ -113,6 +117,7 @@ public class TwitterEngine {
public static TwitterEngine getInstance(Context context) {
if (!mTwitter.isInitialized) {
mTwitter.settings = GlobalSettings.getInstance(context);
mTwitter.accountDB = AccountDatabase.getInstance(context);
if (mTwitter.settings.isLoggedIn()) {
String[] keys = mTwitter.settings.getCurrentUserAccessToken();
mTwitter.aToken = new AccessToken(keys[0], keys[1]);
@ -163,6 +168,7 @@ public class TwitterEngine {
aToken = new AccessToken(key1, key2);
initTwitter();
settings.setConnection(key1, key2, twitter.getId());
accountDB.setLogin(twitter.getId(), key1, key2);
} else {
throw new EngineException(EngineException.InternalErrorType.TOKENNOTSET);
}
@ -415,6 +421,22 @@ public class TwitterEngine {
}
}
/**
* get a list of users
*
* @param users user IDs
* @return list of users
* @throws EngineException if Access is unavailable
*/
public List<User> getUsers(long[] users) throws EngineException {
try {
// todo add paging system
return convertUserList(twitter.lookupUsers(users));
} catch (Exception err) {
throw new EngineException(err);
}
}
/**
* Get User
*
@ -784,14 +806,40 @@ public class TwitterEngine {
try {
DirectMessageList dmList;
int load = settings.getListSize();
if (cursor != null)
if (cursor != null) {
dmList = twitter.getDirectMessages(load, cursor);
else
} else {
dmList = twitter.getDirectMessages(load);
}
MessageList result = new MessageList(cursor, dmList.getNextCursor());
HashMap<Long, User> userMap = new HashMap<Long, User>();
for (DirectMessage dm : dmList) {
try {
result.add(getMessage(dm));
// get sender of the message
User sender;
if (userMap.containsKey(dm.getSenderId())) {
// recycle user information
sender = userMap.get(dm.getSenderId());
} else {
// download new user information
sender = getUser(dm.getSenderId());
userMap.put(dm.getSenderId(), sender);
}
// get receiver of the message
User receiver;
if (userMap.containsKey(dm.getRecipientId())) {
// recycle user information
receiver = userMap.get(dm.getRecipientId());
} else {
// download new user information
receiver = getUser(dm.getRecipientId());
userMap.put(dm.getRecipientId(), receiver);
}
// build message and add to list
Message message = new Message(dm, sender, receiver);
result.add(message);
} catch (EngineException err) {
// ignore messages from suspended/deleted users
}
@ -1144,7 +1192,8 @@ public class TwitterEngine {
* @return User
*/
private List<User> convertUserList(List<twitter4j.User> users) throws TwitterException {
List<User> result = new LinkedList<>();
ArrayList<User> result = new ArrayList<>();
result.ensureCapacity(users.size());
for (twitter4j.User user : users) {
User item = new User(user, twitter.getId());
result.add(item);
@ -1159,28 +1208,10 @@ public class TwitterEngine {
* @return TwitterStatus
*/
private List<Tweet> convertStatusList(List<Status> statuses) throws TwitterException {
List<Tweet> result = new LinkedList<>();
ArrayList<Tweet> result = new ArrayList<>();
result.ensureCapacity(statuses.size());
for (Status status : statuses)
result.add(new Tweet(status, twitter.getId()));
return result;
}
/**
* @param dm Twitter4J direct message
* @return dm item
* @throws EngineException if Access is unavailable
*/
private Message getMessage(DirectMessage dm) throws EngineException {
try {
twitter4j.User receiver;
twitter4j.User sender = twitter.showUser(dm.getSenderId());
if (dm.getSenderId() != dm.getRecipientId())
receiver = twitter.showUser(dm.getRecipientId());
else
receiver = sender;
return new Message(dm, twitter.getId(), sender, receiver);
} catch (Exception err) {
throw new EngineException(err);
}
}
}

View File

@ -0,0 +1,82 @@
package org.nuclearfog.twidda.backend.model;
import androidx.annotation.Nullable;
/**
* container class for user login information
*
* @author nuclearfog
*/
public class Account {
/**
* id of the user
*/
private final long userId;
/**
* date of the first login
*/
private final long loginDate;
/**
* access tokens of the login
*/
private final String key1, key2;
private User user;
public Account(long userId, long loginDate, String key1, String key2) {
this.userId = userId;
this.loginDate = loginDate;
this.key1 = key1;
this.key2 = key2;
}
/**
* get ID of the user
*
* @return user ID
*/
public long getId() {
return userId;
}
/**
* get date of creation
*
* @return date as long
*/
public long getLoginDate() {
return loginDate;
}
/**
* get attached user information
*
* @return user
*/
@Nullable
public User getUser() {
return user;
}
/**
* attach user information
*
* @param user user
*/
public void attachUser(User user) {
this.user = user;
}
/**
* get access tokens
*
* @return array with two access tokens
*/
public String[] getKeys() {
return new String[]{key1, key2};
}
}

View File

@ -25,9 +25,9 @@ public class Message {
* @param sender sender user
* @param receiver receiver user
*/
public Message(DirectMessage dm, long twitterId, twitter4j.User sender, twitter4j.User receiver) {
this.sender = new User(sender, twitterId);
this.receiver = new User(receiver, twitterId);
public Message(DirectMessage dm, User sender, User receiver) {
this.sender = sender;
this.receiver = receiver;
messageId = dm.getId();
time = dm.getCreatedAt().getTime();
setMessageText(dm);

View File

@ -176,7 +176,7 @@ public class User implements Serializable {
}
/**
* get Profile image_add link
* get profile image link
*
* @return link
*/
@ -185,7 +185,7 @@ public class User implements Serializable {
}
/**
* get banner image_add link
* get banner image link
*
* @return link
*/

View File

@ -0,0 +1,140 @@
package org.nuclearfog.twidda.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import org.nuclearfog.twidda.backend.model.Account;
import org.nuclearfog.twidda.database.DatabaseAdapter.LoginTable;
import java.util.ArrayList;
import java.util.List;
import static android.database.sqlite.SQLiteDatabase.CONFLICT_REPLACE;
/**
* this database stores multi user logins
*
* @author nuclearfog
*/
public class AccountDatabase {
/**
* projection of the columns with fixed order
*/
private static final String[] PROJECTION = {
LoginTable.ID,
LoginTable.KEY1,
LoginTable.KEY2,
LoginTable.DATE
};
/**
*
*/
private static final String ACCOUNT_SELECTION = LoginTable.ID + "=?";
/**
* default sort order of the entries
* sort by date of creation, starting with the latest entry
*/
private static final String SORT_BY_CREATION = LoginTable.DATE + " DESC";
/**
* singleton instance
*/
private static final AccountDatabase INSTANCE = new AccountDatabase();
private DatabaseAdapter dataHelper;
private AccountDatabase() {
}
/**
* get singleton instance
*
* @return instance
*/
public static AccountDatabase getInstance(Context context) {
if (INSTANCE.dataHelper == null)
INSTANCE.dataHelper = DatabaseAdapter.getInstance(context.getApplicationContext());
return INSTANCE;
}
/**
* register user login
*
* @param id User ID
* @param key1 access token 1
* @param key2 access token 2
*/
public void setLogin(long id, String key1, String key2) {
ContentValues values = new ContentValues(4);
values.put(LoginTable.ID, id);
values.put(LoginTable.KEY1, key1);
values.put(LoginTable.KEY2, key2);
values.put(LoginTable.DATE, System.currentTimeMillis());
SQLiteDatabase db = dataHelper.getDatabase();
db.beginTransaction();
db.insertWithOnConflict(LoginTable.NAME, "", values, CONFLICT_REPLACE);
db.setTransactionSuccessful();
db.endTransaction();
}
/**
* get all user logins
*
* @return list of all logins
*/
public List<Account> getLogins() {
ArrayList<Account> result = new ArrayList<>();
SQLiteDatabase db = dataHelper.getDatabase();
Cursor cursor = db.query(LoginTable.NAME, PROJECTION, null, null, null, null, SORT_BY_CREATION);
if (cursor.moveToFirst()) {
result.ensureCapacity(cursor.getCount());
do {
long id = cursor.getLong(0);
String key1 = cursor.getString(1);
String key2 = cursor.getString(2);
long date = cursor.getLong(3);
Account account = new Account(id, date, key1, key2);
result.add(account);
} while (cursor.moveToNext());
}
cursor.close();
return result;
}
/**
* remove login information from storage
*
* @param id account ID to remove
*/
public void removeLogin(long id) {
String[] args = {Long.toString(id)};
SQLiteDatabase db = dataHelper.getDatabase();
db.delete(LoginTable.NAME, ACCOUNT_SELECTION, args);
}
/**
* check if user exists
*
* @param id User ID
* @return true if user was found
*/
public boolean exists(long id) {
String[] args = {Long.toString(id)};
SQLiteDatabase db = dataHelper.getDatabase();
Cursor cursor = db.query(LoginTable.NAME, null, ACCOUNT_SELECTION, args, null, null, null, "1");
boolean found = cursor.moveToFirst();
cursor.close();
return found;
}
}

View File

@ -19,22 +19,15 @@ import java.util.regex.Pattern;
import static android.database.sqlite.SQLiteDatabase.CONFLICT_IGNORE;
import static android.database.sqlite.SQLiteDatabase.CONFLICT_REPLACE;
import static org.nuclearfog.twidda.database.DatabaseAdapter.ANSWER_QUERY;
import static org.nuclearfog.twidda.backend.model.Tweet.MediaType.GIF;
import static org.nuclearfog.twidda.backend.model.Tweet.MediaType.IMAGE;
import static org.nuclearfog.twidda.backend.model.Tweet.MediaType.VIDEO;
import static org.nuclearfog.twidda.database.DatabaseAdapter.FavoriteTable;
import static org.nuclearfog.twidda.database.DatabaseAdapter.HOME_QUERY;
import static org.nuclearfog.twidda.database.DatabaseAdapter.MENTION_QUERY;
import static org.nuclearfog.twidda.database.DatabaseAdapter.MESSAGE_QUERY;
import static org.nuclearfog.twidda.database.DatabaseAdapter.MessageTable;
import static org.nuclearfog.twidda.database.DatabaseAdapter.SINGLE_TWEET_QUERY;
import static org.nuclearfog.twidda.database.DatabaseAdapter.STATUS_EXIST_QUERY;
import static org.nuclearfog.twidda.database.DatabaseAdapter.TREND_QUERY;
import static org.nuclearfog.twidda.database.DatabaseAdapter.TWEETFLAG_QUERY;
import static org.nuclearfog.twidda.database.DatabaseAdapter.TrendTable;
import static org.nuclearfog.twidda.database.DatabaseAdapter.TweetRegisterTable;
import static org.nuclearfog.twidda.database.DatabaseAdapter.TweetTable;
import static org.nuclearfog.twidda.database.DatabaseAdapter.USERFAVORIT_QUERY;
import static org.nuclearfog.twidda.database.DatabaseAdapter.USERFLAG_QUERY;
import static org.nuclearfog.twidda.database.DatabaseAdapter.USERTWEET_QUERY;
import static org.nuclearfog.twidda.database.DatabaseAdapter.USER_QUERY;
import static org.nuclearfog.twidda.database.DatabaseAdapter.UserRegisterTable;
import static org.nuclearfog.twidda.database.DatabaseAdapter.UserTable;
/**
@ -45,27 +38,100 @@ import static org.nuclearfog.twidda.database.DatabaseAdapter.UserTable;
*/
public class AppDatabase {
// Tweet flags
// Tweet status bits
private static final int FAV_MASK = 1; // tweet is favored by user
private static final int RTW_MASK = 1 << 1; // tweet is retweeted by user
private static final int HOM_MASK = 1 << 2; // tweet is from home timeline
private static final int MEN_MASK = 1 << 3; // tweet is from mention timeline
private static final int UTW_MASK = 1 << 4; // tweet is from an users timeline
private static final int RPL_MASK = 1 << 5; // tweet is from a reply timeline
// Media content flags
private static final int MEDIA_IMAGE_MASK = 1 << 6; // tweet contains images
private static final int MEDIA_VIDEO_MASK = 2 << 6; // tweet contains a video
private static final int MEDIA_ANGIF_MASK = 3 << 6; // tweet contains an animation
private static final int MEDIA_SENS_MASK = 1 << 8; // tweet contains sensitive media
// user flags
// user status bits
private static final int VER_MASK = 1; // user is verified
private static final int LCK_MASK = 1 << 1; // user is private
private static final int FRQ_MASK = 1 << 2; // a follow request is pending
private static final int EXCL_USR = 1 << 3; // user excluded from mention timeline
private static final int DEF_IMG = 1 << 4; // user has a default profile image
/**
* query to create tweet table with user and register columns
*/
private static final String TWEET_TABLE = TweetTable.NAME
+ " INNER JOIN " + UserTable.NAME
+ " ON " + TweetTable.NAME + "." + TweetTable.USER + "=" + UserTable.NAME + "." + UserTable.ID
+ " INNER JOIN " + UserRegisterTable.NAME
+ " ON " + TweetTable.NAME + "." + TweetTable.USER + "=" + UserRegisterTable.NAME + "." + UserRegisterTable.ID
+ " INNER JOIN " + TweetRegisterTable.NAME
+ " ON " + TweetTable.NAME + "." + TweetTable.ID + "=" + TweetRegisterTable.NAME + "." + TweetRegisterTable.ID;
/**
* query to get user information
*/
private static final String USER_TABLE = "SELECT * FROM " + UserTable.NAME
+ " INNER JOIN " + UserRegisterTable.NAME
+ " ON " + UserTable.NAME + "." + UserTable.ID + "=" + UserRegisterTable.NAME + "." + UserRegisterTable.ID
+ " WHERE " + UserTable.NAME + "." + UserTable.ID + "=? LIMIT 1";
/**
* SQL query to get home timeline tweets
*/
static final String HOME_QUERY = "SELECT * FROM " + TWEET_TABLE
+ " WHERE " + TweetRegisterTable.NAME + "." + TweetRegisterTable.REGISTER + "&" + HOM_MASK + " IS NOT 0"
+ " AND " + TweetRegisterTable.NAME + "." + TweetRegisterTable.OWNER + "=?"
+ " ORDER BY " + TweetTable.ID
+ " DESC LIMIT ?";
/**
* SQL query to get mention timeline
*/
static final String MENTION_QUERY = "SELECT * FROM " + TWEET_TABLE
+ " WHERE " + TweetRegisterTable.NAME + "." + TweetRegisterTable.REGISTER + "&" + MEN_MASK + " IS NOT 0"
+ " AND " + UserRegisterTable.NAME + "." + UserRegisterTable.REGISTER + "&" + EXCL_USR + " IS 0"
+ " AND " + TweetRegisterTable.NAME + "." + TweetRegisterTable.OWNER + "=?"
+ " ORDER BY " + TweetTable.ID
+ " DESC LIMIT ?";
/**
* SQL query to get tweets of an user
*/
static final String USERTWEET_QUERY = "SELECT * FROM " + TWEET_TABLE
+ " WHERE " + TweetRegisterTable.NAME + "." + TweetRegisterTable.REGISTER + "&" + UTW_MASK + " IS NOT 0"
+ " AND " + TweetRegisterTable.NAME + "." + TweetRegisterTable.OWNER + "=?"
+ " AND " + TweetTable.NAME + "." + TweetTable.USER + "=?"
+ " ORDER BY " + TweetTable.ID
+ " DESC LIMIT ?";
/**
* SQL query to get tweets favored by an user
*/
static final String USERFAVORIT_QUERY = "SELECT * FROM " + TWEET_TABLE
+ " INNER JOIN " + FavoriteTable.NAME
+ " ON " + TweetTable.NAME + "." + TweetTable.ID + "=" + FavoriteTable.NAME + "." + FavoriteTable.TWEETID
+ " WHERE " + FavoriteTable.NAME + "." + FavoriteTable.FAVORITEDBY + "=?"
+ " AND " + TweetRegisterTable.NAME + "." + TweetRegisterTable.OWNER + "=?"
+ " ORDER BY " + TweetTable.ID
+ " DESC LIMIT ?";
/**
* SQL query to get a single tweet specified by an ID
*/
static final String SINGLE_TWEET_QUERY = "SELECT * FROM " + TWEET_TABLE
+ " WHERE " + TweetTable.NAME + "." + TweetTable.ID + "=? LIMIT 1";
/**
* SQL query to get replies of a tweet specified by a reply ID
*/
static final String ANSWER_QUERY = "SELECT * FROM " + TWEET_TABLE
+ " WHERE " + TweetTable.NAME + "." + TweetTable.REPLYTWEET + "=?"
+ " AND " + TweetRegisterTable.NAME + "." + TweetRegisterTable.OWNER + "=?"
+ " AND " + TweetRegisterTable.NAME + "." + TweetRegisterTable.REGISTER + "&" + RPL_MASK + " IS NOT 0"
+ " AND " + UserRegisterTable.NAME + "." + UserRegisterTable.REGISTER + "&" + EXCL_USR + " IS 0"
+ " ORDER BY " + TweetTable.ID + " DESC LIMIT ?";
/**
* select tweet entries from favorite table matching tweet ID
* this tweet can be favored by multiple users
@ -95,12 +161,37 @@ public class AppDatabase {
/**
* select tweet from tweet table matching tweet ID
*/
private static final String TWEET_SELECT = TweetTable.TABLE + "." + TweetTable.ID + "=?";
private static final String TWEET_SELECT = TweetTable.NAME + "." + TweetTable.ID + "=?";
/**
* select user from user table matching user ID
*/
private static final String USER_SELECT = UserTable.TABLE + "." + UserTable.ID + "=?";
private static final String USER_SELECT = UserTable.NAME + "." + UserTable.ID + "=?";
/**
* selection to get tweet register
*/
private static final String TWEET_REG_SELECT = TweetRegisterTable.ID + "=? AND " + TweetRegisterTable.OWNER + "=?";
/**
* selection to get user register
*/
private static final String USER_REG_SELECT = UserRegisterTable.ID + "=? AND " + UserRegisterTable.OWNER + "=?";
/**
* default message order by date
*/
private static final String MESSAGE_ORDER = MessageTable.SINCE + " DESC";
/**
* default order for trend rows
*/
private static final String TREND_ORDER = TrendTable.INDEX + " ASC";
/**
* limit for accessing a single row
*/
private static final String SINGLE_ITEM = "1";
/**
* limit of database entries
@ -112,13 +203,16 @@ public class AppDatabase {
*/
private final long homeId;
private DatabaseAdapter dataHelper;
/**
* adapter for the database backend
*/
private DatabaseAdapter adapter;
/**
* initialize database
*/
public AppDatabase(Context context) {
dataHelper = DatabaseAdapter.getInstance(context);
adapter = DatabaseAdapter.getInstance(context);
GlobalSettings settings = GlobalSettings.getInstance(context);
homeId = settings.getCurrentUserId();
limit = settings.getListSize();
@ -208,14 +302,14 @@ public class AppDatabase {
public void storeTrends(List<Trend> trends, int woeId) {
String[] args = {Integer.toString(woeId)};
SQLiteDatabase db = getDbWrite();
db.delete(TrendTable.TABLE, TREND_SELECT, args);
db.delete(TrendTable.NAME, TREND_SELECT, args);
for (Trend trend : trends) {
ContentValues trendColumn = new ContentValues(4);
trendColumn.put(TrendTable.ID, woeId);
trendColumn.put(TrendTable.VOL, trend.getRange());
trendColumn.put(TrendTable.TREND, trend.getName());
trendColumn.put(TrendTable.INDEX, trend.getRank());
db.insert(TrendTable.TABLE, null, trendColumn);
db.insert(TrendTable.NAME, null, trendColumn);
}
commit(db);
}
@ -252,7 +346,7 @@ public class AppDatabase {
* @return tweet list
*/
public List<Tweet> getHomeTimeline() {
String[] args = {Integer.toString(HOM_MASK), Integer.toString(limit)};
String[] args = {Long.toString(homeId), Integer.toString(limit)};
SQLiteDatabase db = getDbRead();
List<Tweet> tweetList = new LinkedList<>();
@ -273,7 +367,9 @@ public class AppDatabase {
* @return tweet list
*/
public List<Tweet> getMentions() {
String[] args = {Integer.toString(MEN_MASK), Integer.toString(EXCL_USR), Integer.toString(limit)};
String[] args = {
Long.toString(homeId), Integer.toString(limit)
};
SQLiteDatabase db = getDbRead();
List<Tweet> tweetList = new LinkedList<>();
@ -295,7 +391,10 @@ public class AppDatabase {
* @return Tweet list of user tweets
*/
public List<Tweet> getUserTweets(long userID) {
String[] args = {Integer.toString(UTW_MASK), Long.toString(userID), Integer.toString(limit)};
String[] args = {
Long.toString(homeId), Long.toString(userID),
Integer.toString(limit)
};
SQLiteDatabase db = getDbRead();
List<Tweet> tweetList = new LinkedList<>();
@ -317,7 +416,10 @@ public class AppDatabase {
* @return favored tweets by user
*/
public List<Tweet> getUserFavorites(long ownerID) {
String[] args = {Long.toString(ownerID), Integer.toString(limit)};
String[] args = {
Long.toString(ownerID), Long.toString(homeId),
Integer.toString(limit)
};
SQLiteDatabase db = getDbRead();
List<Tweet> tweetList = new LinkedList<>();
@ -370,8 +472,10 @@ public class AppDatabase {
* @return list of tweet answers
*/
public List<Tweet> getAnswers(long tweetId) {
String[] args = {Long.toString(tweetId), Integer.toString(RPL_MASK),
Integer.toString(EXCL_USR), Integer.toString(limit)};
String[] args = {
Long.toString(tweetId), Long.toString(homeId),
Integer.toString(limit)
};
SQLiteDatabase db = getDbRead();
List<Tweet> tweetList = new LinkedList<>();
@ -408,8 +512,8 @@ public class AppDatabase {
String[] args = {Long.toString(tweetId)};
SQLiteDatabase db = getDbWrite();
db.delete(TweetTable.TABLE, TWEET_SELECT, args);
db.delete(FavoriteTable.TABLE, FAVORITE_SELECT_TWEET, args);
db.delete(TweetTable.NAME, TWEET_SELECT, args);
db.delete(FavoriteTable.NAME, FAVORITE_SELECT_TWEET, args);
commit(db);
}
@ -423,15 +527,16 @@ public class AppDatabase {
if (tweet.getEmbeddedTweet() != null)
tweetId = tweet.getEmbeddedTweet().getId();
String[] delArgs = {Long.toString(tweetId), Long.toString(homeId)};
String[] updateArgs = {Long.toString(tweetId)};
String[] updateArgs = {Long.toString(tweetId), Long.toString(homeId)};
SQLiteDatabase db = getDbWrite();
int flags = getTweetFlags(db, tweetId);
flags &= ~FAV_MASK;
// get tweet register
ContentValues status = new ContentValues(1);
status.put(TweetTable.REGISTER, flags);
db.delete(FavoriteTable.TABLE, FAVORITE_SELECT, delArgs);
db.update(TweetTable.TABLE, status, TWEET_SELECT, updateArgs);
int flags = getTweetFlags(db, tweetId) & ~FAV_MASK; // unset favorite flag
status.put(TweetRegisterTable.REGISTER, flags);
// update database
db.update(TweetRegisterTable.NAME, status, TWEET_REG_SELECT, updateArgs);
db.delete(FavoriteTable.NAME, FAVORITE_SELECT, delArgs);
commit(db);
}
@ -458,7 +563,7 @@ public class AppDatabase {
String[] args = {Integer.toString(woeId)};
SQLiteDatabase db = getDbRead();
Cursor cursor = db.rawQuery(TREND_QUERY, args);
Cursor cursor = db.query(TrendTable.NAME, null, TREND_SELECT, args, null, null, TREND_ORDER);
List<Trend> trends = new LinkedList<>();
if (cursor.moveToFirst()) {
@ -484,11 +589,11 @@ public class AppDatabase {
* @return list of direct messages
*/
public MessageList getMessages() {
String[] args = {Integer.toString(limit)};
String count = Integer.toString(limit);
// TODO get next cursor from database
MessageList result = new MessageList(null, null);
SQLiteDatabase db = getDbRead();
Cursor cursor = db.rawQuery(MESSAGE_QUERY, args);
Cursor cursor = db.query(MessageTable.TABLE, null, MESSAGE_SELECT, null, null, null, MESSAGE_ORDER, count);
if (cursor.moveToFirst()) {
// get indexes
int idxSender = cursor.getColumnIndexOrThrow(MessageTable.SENDER);
@ -531,18 +636,18 @@ public class AppDatabase {
* @param mute true remove user tweets from mention results
*/
public void muteUser(long id, boolean mute) {
String[] args = {Long.toString(id)};
String[] args = {Long.toString(id), Long.toString(homeId)};
SQLiteDatabase db = getDbWrite();
int flags = getUserFlags(db, id);
if (mute)
if (mute) {
flags |= EXCL_USR;
else
} else {
flags &= ~EXCL_USR;
ContentValues userColumn = new ContentValues(1);
userColumn.put(UserTable.REGISTER, flags);
db.update(UserTable.TABLE, userColumn, USER_SELECT, args);
}
ContentValues registerColumn = new ContentValues(1);
registerColumn.put(UserRegisterTable.REGISTER, flags);
db.update(UserRegisterTable.NAME, registerColumn, USER_REG_SELECT, args);
commit(db);
}
@ -554,37 +659,37 @@ public class AppDatabase {
*/
private Tweet getStatus(Cursor cursor) {
long time = cursor.getLong(cursor.getColumnIndexOrThrow(TweetTable.SINCE));
String tweettext = cursor.getString(cursor.getColumnIndexOrThrow(TweetTable.TWEET));
String text = cursor.getString(cursor.getColumnIndexOrThrow(TweetTable.TWEET));
int retweet = cursor.getInt(cursor.getColumnIndexOrThrow(TweetTable.RETWEET));
int favorit = cursor.getInt(cursor.getColumnIndexOrThrow(TweetTable.FAVORITE));
int favorite = cursor.getInt(cursor.getColumnIndexOrThrow(TweetTable.FAVORITE));
long tweetId = cursor.getLong(cursor.getColumnIndexOrThrow(TweetTable.ID));
long retweetId = cursor.getLong(cursor.getColumnIndexOrThrow(TweetTable.RETWEETID));
String replyname = cursor.getString(cursor.getColumnIndexOrThrow(TweetTable.REPLYNAME));
String replyName = cursor.getString(cursor.getColumnIndexOrThrow(TweetTable.REPLYNAME));
long replyStatusId = cursor.getLong(cursor.getColumnIndexOrThrow(TweetTable.REPLYTWEET));
long retweeterId = cursor.getLong(cursor.getColumnIndexOrThrow(TweetTable.RETWEETUSER));
String source = cursor.getString(cursor.getColumnIndexOrThrow(TweetTable.SOURCE));
String medialinks = cursor.getString(cursor.getColumnIndexOrThrow(TweetTable.MEDIA));
String links = cursor.getString(cursor.getColumnIndexOrThrow(TweetTable.MEDIA));
String place = cursor.getString(cursor.getColumnIndexOrThrow(TweetTable.PLACE));
String geo = cursor.getString(cursor.getColumnIndexOrThrow(TweetTable.COORDINATE));
long replyUserId = cursor.getLong(cursor.getColumnIndexOrThrow(TweetTable.REPLYUSER));
int statusregister = cursor.getInt(cursor.getColumnIndexOrThrow(TweetTable.REGISTER));
boolean favorited = (statusregister & FAV_MASK) != 0;
boolean retweeted = (statusregister & RTW_MASK) != 0;
boolean sensitive = (statusregister & MEDIA_SENS_MASK) != 0;
String[] medias = parseMedia(medialinks);
int tweetRegister = cursor.getInt(cursor.getColumnIndexOrThrow(TweetRegisterTable.REGISTER));
boolean favorited = (tweetRegister & FAV_MASK) != 0;
boolean retweeted = (tweetRegister & RTW_MASK) != 0;
boolean sensitive = (tweetRegister & MEDIA_SENS_MASK) != 0;
String[] medias = parseMedia(links);
// get media type
Tweet.MediaType mediaType = Tweet.MediaType.NONE;
if ((statusregister & MEDIA_ANGIF_MASK) == MEDIA_ANGIF_MASK)
mediaType = Tweet.MediaType.GIF;
else if ((statusregister & MEDIA_IMAGE_MASK) == MEDIA_IMAGE_MASK)
mediaType = Tweet.MediaType.IMAGE;
else if ((statusregister & MEDIA_VIDEO_MASK) == MEDIA_VIDEO_MASK)
if ((tweetRegister & MEDIA_ANGIF_MASK) == MEDIA_ANGIF_MASK)
mediaType = GIF;
else if ((tweetRegister & MEDIA_IMAGE_MASK) == MEDIA_IMAGE_MASK)
mediaType = IMAGE;
else if ((tweetRegister & MEDIA_VIDEO_MASK) == MEDIA_VIDEO_MASK)
mediaType = Tweet.MediaType.VIDEO;
User user = getUser(cursor);
Tweet embeddedTweet = null;
if (retweetId > 1)
embeddedTweet = getStatus(retweetId);
return new Tweet(tweetId, retweet, favorit, user, tweettext, time, replyname, replyUserId, medias,
return new Tweet(tweetId, retweet, favorite, user, text, time, replyName, replyUserId, medias,
mediaType, source, replyStatusId, embeddedTweet, retweeterId, retweeted, favorited, sensitive, place, geo);
}
@ -598,7 +703,8 @@ public class AppDatabase {
@Nullable
private User getUser(long userId, SQLiteDatabase db) {
String[] args = {Long.toString(userId)};
Cursor cursor = db.rawQuery(USER_QUERY, args);
Cursor cursor = db.rawQuery(USER_TABLE, args);
User user = null;
if (cursor.moveToFirst())
@ -616,8 +722,7 @@ public class AppDatabase {
private User getUser(Cursor cursor) {
long userId = cursor.getLong(cursor.getColumnIndexOrThrow(UserTable.ID));
String username = cursor.getString(cursor.getColumnIndexOrThrow(UserTable.USERNAME));
String screenname = cursor.getString(cursor.getColumnIndexOrThrow(UserTable.SCREENNAME));
int userRegister = cursor.getInt(cursor.getColumnIndexOrThrow(UserTable.REGISTER));
String screenName = cursor.getString(cursor.getColumnIndexOrThrow(UserTable.SCREENNAME));
String profileImg = cursor.getString(cursor.getColumnIndexOrThrow(UserTable.IMAGE));
String bio = cursor.getString(cursor.getColumnIndexOrThrow(UserTable.DESCRIPTION));
String link = cursor.getString(cursor.getColumnIndexOrThrow(UserTable.LINK));
@ -628,12 +733,13 @@ public class AppDatabase {
int follower = cursor.getInt(cursor.getColumnIndexOrThrow(UserTable.FOLLOWER));
int tCount = cursor.getInt(cursor.getColumnIndexOrThrow(UserTable.TWEETS));
int fCount = cursor.getInt(cursor.getColumnIndexOrThrow(UserTable.FAVORS));
int userRegister = cursor.getInt(cursor.getColumnIndexOrThrow(UserRegisterTable.REGISTER));
boolean isCurrentUser = homeId == userId;
boolean isVerified = (userRegister & VER_MASK) != 0;
boolean isLocked = (userRegister & LCK_MASK) != 0;
boolean isReq = (userRegister & FRQ_MASK) != 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, isCurrentUser, isVerified,
isLocked, isReq, defaultImg, link, banner, createdAt, following, follower, tCount, fCount);
}
@ -645,7 +751,8 @@ public class AppDatabase {
* @param mode SQLITE mode {@link SQLiteDatabase#CONFLICT_IGNORE} or {@link SQLiteDatabase#CONFLICT_REPLACE}
*/
private void storeUser(User user, SQLiteDatabase db, int mode) {
ContentValues userColumn = new ContentValues(14);
ContentValues userRegister = new ContentValues(3);
ContentValues userColumn = new ContentValues(13);
int flags = getUserFlags(db, user.getId());
if (user.isVerified())
flags |= VER_MASK;
@ -668,7 +775,6 @@ public class AppDatabase {
userColumn.put(UserTable.USERNAME, user.getUsername());
userColumn.put(UserTable.SCREENNAME, user.getScreenname());
userColumn.put(UserTable.IMAGE, user.getImageLink());
userColumn.put(UserTable.REGISTER, flags);
userColumn.put(UserTable.DESCRIPTION, user.getBio());
userColumn.put(UserTable.LINK, user.getLink());
userColumn.put(UserTable.LOCATION, user.getLocation());
@ -679,7 +785,12 @@ public class AppDatabase {
userColumn.put(UserTable.TWEETS, user.getTweetCount());
userColumn.put(UserTable.FAVORS, user.getFavorCount());
db.insertWithOnConflict(UserTable.TABLE, null, userColumn, mode);
userRegister.put(UserRegisterTable.ID, user.getId());
userRegister.put(UserRegisterTable.OWNER, homeId);
userRegister.put(UserRegisterTable.REGISTER, flags);
db.insertWithOnConflict(UserTable.NAME, null, userColumn, mode);
db.insertWithOnConflict(UserRegisterTable.NAME, null, userRegister, mode);
}
@ -691,11 +802,11 @@ public class AppDatabase {
* @param db SQLite database
*/
private void storeStatus(Tweet tweet, int statusRegister, SQLiteDatabase db) {
ContentValues status = new ContentValues(17);
ContentValues register = new ContentValues(3);
ContentValues status = new ContentValues(16);
User user = tweet.getUser();
Tweet rtStat = tweet.getEmbeddedTweet();
long rtId = -1L;
if (rtStat != null) {
storeStatus(rtStat, 0, db);
rtId = rtStat.getId();
@ -716,21 +827,14 @@ public class AppDatabase {
} else {
statusRegister &= ~MEDIA_SENS_MASK;
}
switch (tweet.getMediaType()) {
case IMAGE:
statusRegister |= MEDIA_IMAGE_MASK;
break;
case VIDEO:
statusRegister |= MEDIA_VIDEO_MASK;
break;
case GIF:
statusRegister |= MEDIA_ANGIF_MASK;
break;
if (tweet.getMediaType() == IMAGE) {
statusRegister |= MEDIA_IMAGE_MASK;
} else if (tweet.getMediaType() == VIDEO) {
statusRegister |= MEDIA_VIDEO_MASK;
} else if (tweet.getMediaType() == GIF) {
statusRegister |= MEDIA_ANGIF_MASK;
}
status.put(TweetTable.MEDIA, getMediaLinks(tweet));
status.put(TweetTable.REGISTER, statusRegister);
status.put(TweetTable.ID, tweet.getId());
status.put(TweetTable.USER, user.getId());
status.put(TweetTable.SINCE, tweet.getTime());
@ -746,8 +850,14 @@ public class AppDatabase {
status.put(TweetTable.COORDINATE, tweet.getLocationCoordinates());
status.put(TweetTable.REPLYUSER, tweet.getReplyUserId());
status.put(TweetTable.REPLYNAME, tweet.getReplyName());
register.put(TweetRegisterTable.ID, tweet.getId());
register.put(TweetRegisterTable.OWNER, homeId);
register.put(TweetRegisterTable.REGISTER, statusRegister);
storeUser(user, db, CONFLICT_IGNORE);
db.insertWithOnConflict(TweetTable.TABLE, null, status, CONFLICT_REPLACE);
db.insertWithOnConflict(TweetTable.NAME, null, status, CONFLICT_REPLACE);
db.insertWithOnConflict(TweetRegisterTable.NAME, null, register, CONFLICT_REPLACE);
}
/**
@ -758,26 +868,31 @@ public class AppDatabase {
*/
private void updateStatus(Tweet tweet, SQLiteDatabase db) {
String[] tweetIdArg = {Long.toString(tweet.getId())};
String[] tweetRegArg = {Long.toString(tweet.getId()), Long.toString(homeId)};
String[] userIdArg = {Long.toString(tweet.getUser().getId())};
ContentValues statColumn = new ContentValues(7);
ContentValues tweetColumn = new ContentValues(6);
ContentValues tweetRegColumn = new ContentValues(3);
ContentValues userColumn = new ContentValues(9);
int flags = getTweetFlags(db, tweet.getId());
int register = getTweetFlags(db, tweet.getId());
if (tweet.retweeted())
flags |= RTW_MASK;
register |= RTW_MASK;
else
flags &= ~RTW_MASK;
register &= ~RTW_MASK;
if (tweet.favored())
flags |= FAV_MASK;
register |= FAV_MASK;
else
flags &= ~FAV_MASK;
statColumn.put(TweetTable.TWEET, tweet.getTweet());
statColumn.put(TweetTable.RETWEET, tweet.getRetweetCount());
statColumn.put(TweetTable.FAVORITE, tweet.getFavoriteCount());
statColumn.put(TweetTable.RETWEETUSER, tweet.getMyRetweetId());
statColumn.put(TweetTable.REPLYNAME, tweet.getReplyName());
statColumn.put(TweetTable.REGISTER, flags);
statColumn.put(TweetTable.MEDIA, getMediaLinks(tweet));
register &= ~FAV_MASK;
tweetColumn.put(TweetTable.TWEET, tweet.getTweet());
tweetColumn.put(TweetTable.RETWEET, tweet.getRetweetCount());
tweetColumn.put(TweetTable.FAVORITE, tweet.getFavoriteCount());
tweetColumn.put(TweetTable.RETWEETUSER, tweet.getMyRetweetId());
tweetColumn.put(TweetTable.REPLYNAME, tweet.getReplyName());
tweetColumn.put(TweetTable.MEDIA, getMediaLinks(tweet));
tweetRegColumn.put(TweetRegisterTable.ID, tweet.getId());
tweetRegColumn.put(TweetRegisterTable.OWNER, homeId);
tweetRegColumn.put(TweetRegisterTable.REGISTER, register);
User user = tweet.getUser();
userColumn.put(UserTable.USERNAME, user.getUsername());
@ -790,8 +905,9 @@ public class AppDatabase {
userColumn.put(UserTable.FRIENDS, user.getFollowing());
userColumn.put(UserTable.FOLLOWER, user.getFollower());
db.update(TweetTable.TABLE, statColumn, TWEET_SELECT, tweetIdArg);
db.update(UserTable.TABLE, userColumn, USER_SELECT, userIdArg);
db.update(TweetTable.NAME, tweetColumn, TWEET_SELECT, tweetIdArg);
db.update(TweetRegisterTable.NAME, tweetRegColumn, TWEET_REG_SELECT, tweetRegArg);
db.update(UserTable.NAME, userColumn, USER_SELECT, userIdArg);
}
/**
@ -805,7 +921,7 @@ public class AppDatabase {
ContentValues favTable = new ContentValues(2);
favTable.put(FavoriteTable.TWEETID, tweetId);
favTable.put(FavoriteTable.FAVORITEDBY, ownerId);
db.insertWithOnConflict(FavoriteTable.TABLE, null, favTable, CONFLICT_REPLACE);
db.insertWithOnConflict(FavoriteTable.NAME, null, favTable, CONFLICT_REPLACE);
}
/**
@ -816,7 +932,7 @@ public class AppDatabase {
*/
private void removeOldFavorites(SQLiteDatabase db, long userId) {
String[] delArgs = {Long.toString(userId)};
db.delete(FavoriteTable.TABLE, FAVORITE_SELECT_OWNER, delArgs);
db.delete(FavoriteTable.NAME, FAVORITE_SELECT_OWNER, delArgs);
}
/**
@ -845,14 +961,13 @@ public class AppDatabase {
* @return tweet flags
*/
private int getTweetFlags(SQLiteDatabase db, long tweetID) {
String[] args = {Long.toString(tweetID)};
String[] columns = {TweetRegisterTable.REGISTER};
String[] args = {Long.toString(tweetID), Long.toString(homeId)};
Cursor c = db.rawQuery(TWEETFLAG_QUERY, args);
Cursor c = db.query(TweetRegisterTable.NAME, columns, TWEET_REG_SELECT, args, null, null, null, SINGLE_ITEM);
int result = 0;
if (c.moveToFirst()) {
int pos = c.getColumnIndexOrThrow(TweetTable.REGISTER);
result = c.getInt(pos);
}
if (c.moveToFirst())
result = c.getInt(0);
c.close();
return result;
}
@ -865,14 +980,13 @@ public class AppDatabase {
* @return user flags
*/
private int getUserFlags(SQLiteDatabase db, long userID) {
String[] args = {Long.toString(userID)};
String[] columns = {UserRegisterTable.REGISTER};
String[] args = {Long.toString(userID), Long.toString(homeId)};
Cursor c = db.rawQuery(USERFLAG_QUERY, args);
Cursor c = db.query(UserRegisterTable.NAME, columns, USER_REG_SELECT, args, null, null, null, SINGLE_ITEM);
int result = 0;
if (c.moveToFirst()) {
int pos = c.getColumnIndexOrThrow(UserTable.REGISTER);
result = c.getInt(pos);
}
if (c.moveToFirst())
result = c.getInt(0);
c.close();
return result;
}
@ -887,7 +1001,7 @@ public class AppDatabase {
private boolean containStatus(long id, SQLiteDatabase db) {
String[] args = {Long.toString(id)};
Cursor c = db.rawQuery(STATUS_EXIST_QUERY, args);
Cursor c = db.query(TweetTable.NAME, null, TWEET_SELECT, args, null, null, SINGLE_ITEM);
boolean result = c.moveToFirst();
c.close();
return result;
@ -899,7 +1013,7 @@ public class AppDatabase {
* @return SQLite instance
*/
private synchronized SQLiteDatabase getDbRead() {
return dataHelper.getDatabase();
return adapter.getDatabase();
}
/**
@ -908,7 +1022,7 @@ public class AppDatabase {
* @return SQLite instance
*/
private synchronized SQLiteDatabase getDbWrite() {
SQLiteDatabase db = dataHelper.getDatabase();
SQLiteDatabase db = adapter.getDatabase();
db.beginTransaction();
return db;
}

View File

@ -23,7 +23,7 @@ public class DatabaseAdapter {
* SQL query to create a table for user information
*/
private static final String TABLE_USER = "CREATE TABLE IF NOT EXISTS "
+ UserTable.TABLE + "("
+ UserTable.NAME + "("
+ UserTable.ID + " INTEGER PRIMARY KEY,"
+ UserTable.USERNAME + " TEXT,"
+ UserTable.SCREENNAME + " TEXT,"
@ -32,7 +32,6 @@ public class DatabaseAdapter {
+ UserTable.DESCRIPTION + " TEXT,"
+ UserTable.LOCATION + " TEXT,"
+ UserTable.LINK + " TEXT,"
+ UserTable.REGISTER + " INTEGER,"
+ UserTable.SINCE + " INTEGER,"
+ UserTable.FRIENDS + " INTEGER,"
+ UserTable.FOLLOWER + " INTEGER,"
@ -43,7 +42,7 @@ public class DatabaseAdapter {
* SQL query to create a table for tweet information
*/
private static final String TABLE_TWEET = "CREATE TABLE IF NOT EXISTS "
+ TweetTable.TABLE + "("
+ TweetTable.NAME + "("
+ TweetTable.ID + " INTEGER PRIMARY KEY,"
+ TweetTable.USER + " INTEGER,"
+ TweetTable.RETWEETID + " INTEGER,"
@ -56,30 +55,29 @@ public class DatabaseAdapter {
+ TweetTable.MEDIA + " TEXT,"
+ TweetTable.RETWEET + " INTEGER,"
+ TweetTable.FAVORITE + " INTEGER,"
+ TweetTable.REGISTER + " INTEGER,"
+ TweetTable.SOURCE + " TEXT,"
+ TweetTable.PLACE + " TEXT,"
+ TweetTable.COORDINATE + " TEXT,"
+ "FOREIGN KEY(" + TweetTable.USER + ")"
+ "REFERENCES " + UserTable.TABLE + "(" + UserTable.ID + "));";
+ "REFERENCES " + UserTable.NAME + "(" + UserTable.ID + "));";
/**
* SQL query to create a table for favorite tweets
*/
private static final String TABLE_FAVORS = "CREATE TABLE IF NOT EXISTS "
+ FavoriteTable.TABLE + "("
+ FavoriteTable.NAME + "("
+ FavoriteTable.FAVORITEDBY + " INTEGER,"
+ FavoriteTable.TWEETID + " INTEGER,"
+ "FOREIGN KEY(" + FavoriteTable.FAVORITEDBY + ")"
+ "REFERENCES " + UserTable.TABLE + "(" + UserTable.ID + "),"
+ "REFERENCES " + UserTable.NAME + "(" + UserTable.ID + "),"
+ "FOREIGN KEY(" + FavoriteTable.TWEETID + ")"
+ "REFERENCES " + TweetTable.TABLE + "(" + TweetTable.ID + "));";
+ "REFERENCES " + TweetTable.NAME + "(" + TweetTable.ID + "));";
/**
* SQL query to create a table for trend information
*/
private static final String TABLE_TRENDS = "CREATE TABLE IF NOT EXISTS "
+ TrendTable.TABLE + "("
+ TrendTable.NAME + "("
+ TrendTable.ID + " INTEGER,"
+ TrendTable.INDEX + " INTEGER,"
+ TrendTable.VOL + " INTEGER,"
@ -96,142 +94,64 @@ public class DatabaseAdapter {
+ MessageTable.RECEIVER + " INTEGER,"
+ MessageTable.MESSAGE + " TEXT);";
/**
*
*/
private static final String TABLE_TWEET_REGISTER = "CREATE TABLE IF NOT EXISTS "
+ TweetRegisterTable.NAME + "("
+ TweetRegisterTable.ID + " INTEGER PRIMARY KEY,"
+ TweetRegisterTable.OWNER + " INTEGER,"
+ TweetRegisterTable.REGISTER + " INTEGER);";
/**
*
*/
private static final String TABLE_USER_REGISTER = "CREATE TABLE IF NOT EXISTS "
+ UserRegisterTable.NAME + "("
+ UserRegisterTable.ID + " INTEGER PRIMARY KEY,"
+ UserRegisterTable.OWNER + " INTEGER,"
+ UserRegisterTable.REGISTER + " INTEGER);";
/**
* SQL query to create a table for user logins
*/
private static final String TABLE_LOGINS = "CREATE TABLE IF NOT EXISTS "
+ LoginTable.NAME + "("
+ LoginTable.ID + " INTEGER PRIMARY KEY,"
+ LoginTable.DATE + " INTEGER,"
+ LoginTable.KEY1 + " TEXT,"
+ LoginTable.KEY2 + " TEXT);";
/**
* index for tweet table
*/
private static final String INDX_TWEET = "CREATE INDEX IF NOT EXISTS idx_tweet"
+ " ON " + TweetTable.TABLE + "(" + TweetTable.USER + "," + TweetTable.REGISTER + ");";
/**
* index for favorite table
*/
private static final String INDX_FAVOR = "CREATE INDEX IF NOT EXISTS idx_favor"
+ " ON " + FavoriteTable.TABLE + "(" + FavoriteTable.FAVORITEDBY + "," + FavoriteTable.TWEETID + ");";
+ " ON " + TweetTable.NAME + "(" + TweetTable.USER + ");";
/**
* index for trend table
*/
private static final String INDX_TREND = "CREATE INDEX IF NOT EXISTS idx_trend"
+ " ON " + TrendTable.TABLE + "(" + TrendTable.ID + ");";
+ " ON " + TrendTable.NAME + "(" + TrendTable.ID + ");";
/**
* update for the tweet table
*/
private static final String TABLE_TWEET_ADD_PLACE = "ALTER TABLE " + TweetTable.TABLE
private static final String TABLE_TWEET_ADD_PLACE = "ALTER TABLE " + TweetTable.NAME
+ " ADD COLUMN " + TweetTable.PLACE + " TEXT";
/**
* update for the tweet table
*/
private static final String TABLE_TWEET_ADD_GEO = "ALTER TABLE " + TweetTable.TABLE
private static final String TABLE_TWEET_ADD_GEO = "ALTER TABLE " + TweetTable.NAME
+ " ADD COLUMN " + TweetTable.COORDINATE + " TEXT";
/**
* update for the trend table
*/
private static final String TABLE_TREND_ADD_VOL = "ALTER TABLE " + TrendTable.TABLE
private static final String TABLE_TREND_ADD_VOL = "ALTER TABLE " + TrendTable.NAME
+ " ADD COLUMN " + TrendTable.VOL + " INTEGER";
/**
*
*/
private static final String USERTWEET_TABLE = TweetTable.TABLE
+ " INNER JOIN " + UserTable.TABLE
+ " ON " + TweetTable.TABLE + "." + TweetTable.USER + "=" + UserTable.TABLE + "." + UserTable.ID;
/**
* SQL query to get home timeline tweets
*/
static final String HOME_QUERY = "SELECT * FROM " + USERTWEET_TABLE
+ " WHERE " + TweetTable.REGISTER + "&? IS NOT 0"
+ " ORDER BY " + TweetTable.ID
+ " DESC LIMIT ?";
/**
* SQL query to get mention timeline
*/
static final String MENTION_QUERY = "SELECT * FROM " + USERTWEET_TABLE
+ " WHERE " + TweetTable.REGISTER + "&? IS NOT 0"
+ " AND " + UserTable.REGISTER + "&? IS 0"
+ " ORDER BY " + TweetTable.ID
+ " DESC LIMIT ?";
/**
* SQL query to get tweets of an user
*/
static final String USERTWEET_QUERY = "SELECT * FROM " + USERTWEET_TABLE
+ " WHERE " + TweetTable.REGISTER + "&? IS NOT 0"
+ " AND " + TweetTable.TABLE + "." + TweetTable.USER + "=?"
+ " ORDER BY " + TweetTable.ID
+ " DESC LIMIT ?";
/**
* SQL query to get tweets favored by an user
*/
static final String USERFAVORIT_QUERY = "SELECT * FROM " + USERTWEET_TABLE
+ " INNER JOIN " + FavoriteTable.TABLE
+ " ON " + TweetTable.TABLE + "." + TweetTable.ID + "=" + FavoriteTable.TABLE + "." + FavoriteTable.TWEETID
+ " WHERE " + FavoriteTable.FAVORITEDBY + "=?"
+ " ORDER BY " + TweetTable.ID
+ " DESC LIMIT ?";
/**
* SQL query to get a single tweet specified by an ID
*/
static final String SINGLE_TWEET_QUERY = "SELECT * FROM " + USERTWEET_TABLE
+ " WHERE " + TweetTable.TABLE + "." + TweetTable.ID + "=? LIMIT 1";
/**
* SQL query to get replies of a tweet specified by a reply ID
*/
static final String ANSWER_QUERY = "SELECT * FROM " + USERTWEET_TABLE
+ " WHERE " + TweetTable.TABLE + "." + TweetTable.REPLYTWEET + "=?"
+ " AND " + TweetTable.REGISTER + "&? IS NOT 0"
+ " AND " + UserTable.REGISTER + "&? IS 0"
+ " ORDER BY " + TweetTable.ID + " DESC LIMIT ?";
/**
* SQL query to get locale based trends
*/
static final String TREND_QUERY = "SELECT * FROM " + TrendTable.TABLE
+ " WHERE " + TrendTable.ID + "=?"
+ " ORDER BY " + TrendTable.INDEX + " ASC";
/**
* SQL query to get direct messages
*/
static final String MESSAGE_QUERY = "SELECT * FROM " + MessageTable.TABLE
+ " ORDER BY " + MessageTable.ID + " DESC "
+ "LIMIT ?";
/**
* SQL query to get user information
*/
static final String USER_QUERY = "SELECT * FROM " + UserTable.TABLE
+ " WHERE " + UserTable.ID + "=?"
+ " LIMIT 1";
/**
* SQL query to get a status register for a tweet
*/
static final String TWEETFLAG_QUERY = "SELECT " + TweetTable.REGISTER + " FROM " + TweetTable.TABLE
+ " WHERE " + TweetTable.ID + "=?"
+ " LIMIT 1;";
/**
* SQL query to get a status register of an user
*/
static final String USERFLAG_QUERY = "SELECT " + UserTable.REGISTER + " FROM " + UserTable.TABLE
+ " WHERE " + UserTable.ID + "=?"
+ " LIMIT 1;";
/**
* SQL query to check if a status exists in database
*/
static final String STATUS_EXIST_QUERY = "SELECT * FROM " + TweetTable.TABLE
+ " WHERE " + TweetTable.ID + "=?"
+ " LIMIT 1;";
/**
* singleton instance
*/
@ -313,9 +233,11 @@ public class DatabaseAdapter {
db.execSQL(TABLE_FAVORS);
db.execSQL(TABLE_TRENDS);
db.execSQL(TABLE_MESSAGES);
db.execSQL(TABLE_LOGINS);
db.execSQL(INDX_TWEET);
db.execSQL(INDX_FAVOR);
db.execSQL(INDX_TREND);
db.execSQL(TABLE_TWEET_REGISTER);
db.execSQL(TABLE_USER_REGISTER);
/// Database just created? set current version
if (db.getVersion() == 0) {
db.setVersion(LATEST_VERSION);
@ -326,66 +248,179 @@ public class DatabaseAdapter {
* table for user information
*/
public interface UserTable {
// table name
String TABLE = "user";
// user information
/**
* table name
*/
String NAME = "user";
/**
* ID of the user
*/
String ID = "userID";
/**
* user name
*/
String USERNAME = "username";
/**
* screen name (starting with @)
*/
String SCREENNAME = "scrname";
/**
* description (bio) of the user
*/
String DESCRIPTION = "bio";
/**
* location attached to profile
*/
String LOCATION = "location";
/**
* link attached to profile
*/
String LINK = "link";
/**
* date of account creation
*/
String SINCE = "createdAt";
// image links
/**
* link to the original profile image
*/
String IMAGE = "pbLink";
/**
* link to the original banner image
*/
String BANNER = "banner";
// connections
/**
* following count
*/
String FRIENDS = "following";
/**
* follower count
*/
String FOLLOWER = "follower";
// tweet count of the user
/**
* count of tweets written/retweeted by user
*/
String TWEETS = "tweetCount";
/**
* count of the tweets favored by the user
*/
String FAVORS = "favorCount";
// integer register containing status bits
String REGISTER = "userregister";
}
/**
* table for all tweets
*/
public interface TweetTable {
// table name
String TABLE = "tweet";
// tweet information
/**
* table name
*/
String NAME = "tweet";
/**
* ID of the tweet
*/
String ID = "tweetID";
/**
* ID of the author
*/
String USER = "userID";
/**
* tweet text
*/
String TWEET = "tweet";
/**
* media links attached to the tweet
*/
String MEDIA = "media";
/**
* retweet count
*/
String RETWEET = "retweet";
/**
* favorite count
*/
String FAVORITE = "favorite";
/**
* timestamp of the tweet
*/
String SINCE = "time";
/**
* API source of the tweet
*/
String SOURCE = "source";
// tweet location
/**
* place name of the tweet
*/
String PLACE = "place";
/**
* GPS coordinate of the tweet
*/
String COORDINATE = "geo";
// information about the replied tweet
/**
* ID of the re plied tweet
*/
String REPLYTWEET = "replyID";
/**
* ID of the replied user
*/
String REPLYUSER = "replyUserID";
/**
* name of the replied user
*/
String REPLYNAME = "replyname";
// information about the retweeter
/**
* ID of the embedded (retweeted) status
*/
String RETWEETID = "retweetID";
/**
* ID of the
*/
String RETWEETUSER = "retweeterID";
// register containing status bits
String REGISTER = "statusregister";
}
/**
* table for favored tweets of an user
*/
public interface FavoriteTable {
// table name
String TABLE = "favorit";
//
/**
* table name
*/
String NAME = "favorit";
/**
* ID of the tweet
*/
String TWEETID = "tweetID";
/**
* ID of the user of this favored tweet
*/
String FAVORITEDBY = "ownerID";
}
@ -393,12 +428,29 @@ public class DatabaseAdapter {
* table for twitter trends
*/
public interface TrendTable {
// tale name
String TABLE = "trend";
// trend information
/**
* table name
*/
String NAME = "trend";
/**
* ID of the trend location
*/
String ID = "woeID";
/**
* rank of the trend
*/
String INDEX = "trendpos";
/**
* popularity count
*/
String VOL = "vol";
/**
* name of the trend
*/
String TREND = "trendname";
}
@ -406,13 +458,121 @@ public class DatabaseAdapter {
* Table for direct messages
*/
public interface MessageTable {
// table name
/**
* table name
*/
String TABLE = "message";
// message information
/**
* ID of the message
*/
String ID = "messageID";
/**
* date of the message
*/
String SINCE = "time";
/**
* User ID of the sender
*/
String SENDER = "senderID";
/**
* User ID of the receiver
*/
String RECEIVER = "receiverID";
/**
* message text
*/
String MESSAGE = "message";
}
/**
* Table for multi user login information
*/
public interface LoginTable {
/**
* SQL table name
*/
String NAME = "login";
/**
* ID of the user
*/
String ID = "userID";
/**
* date of login
*/
String DATE = "date";
/**
* primary oauth access token
*/
String KEY1 = "auth_key1";
/**
* second oauth access token
*/
String KEY2 = "auth_key2";
}
/**
* table for tweet register
* <p>
* a register contains status flags (status bits) of a tweet
* every flag stands for a status like retweeted or favored
* the idea is to save space by putting boolean rows into a single integer row
* <p>
* to avoid conflicts between multi users,
* every login has its own status registers
*/
public interface TweetRegisterTable {
/**
* SQL table name
*/
String NAME = "tweetFlags";
/**
* ID of the user this register references to
*/
String ID = "tweetID";
/**
* ID of the current user accessing the database
*/
String OWNER = "ownerID";
/**
* Register with status bits
*/
String REGISTER = "tweetRegister";
}
/**
* table for user register
*/
public interface UserRegisterTable {
/**
* SQL table name
*/
String NAME = "userFlags";
/**
* ID of the user this register references to
*/
String ID = "userID";
/**
* ID of the current user accessing the database
*/
String OWNER = "ownerID";
/**
* Register with status bits
*/
String REGISTER = "userRegister";
}
}

View File

@ -15,6 +15,7 @@ import static android.content.Context.MODE_PRIVATE;
import static android.graphics.Typeface.DEFAULT;
import static android.graphics.Typeface.MONOSPACE;
import static android.graphics.Typeface.NORMAL;
import static android.graphics.Typeface.SANS_SERIF;
import static android.graphics.Typeface.SERIF;
/**
@ -44,17 +45,27 @@ public class GlobalSettings {
*/
public static final String BANNER_IMG_MID_RES = "/600x200";
/**
* custom android font
*/
private static final Typeface SANS_SERIF_THIN = Typeface.create("sans-serif-thin", NORMAL);
/**
* custom font families from android system
*/
public static final Typeface[] FONTS = {DEFAULT, MONOSPACE, SERIF, Typeface.create("sans-serif-thin", NORMAL)};
public static final Typeface[] FONTS = {DEFAULT, MONOSPACE, SERIF, SANS_SERIF, SANS_SERIF_THIN};
/**
* names of the font types {@link #FONTS}
*/
public static final String[] FONT_NAMES = {"Default", "Monospace", "Serif", "sans-serif-thin"};
public static final String[] FONT_NAMES = {"Default", "Monospace", "Serif", "Sans-Serif", "sans-serif-thin"};
// Setting names stored in SharedPreference
/**
* singleton instance
*/
private static final GlobalSettings ourInstance = new GlobalSettings();
// App preference names
private static final String BACKGROUND_COLOR = "background_color";
private static final String HIGHLIGHT_COLOR = "highlight_color";
private static final String FONT_COLOR = "font_color";
@ -67,10 +78,6 @@ public class GlobalSettings {
private static final String IMAGE_QUALITY = "image_hq";
private static final String ANSWER_LOAD = "answer_load";
private static final String PROFILE_OVERLAP = "profile_toolbar_overlap";
private static final String LOGGED_IN = "login";
private static final String AUTH_KEY1 = "key1";
private static final String AUTH_KEY2 = "key2";
private static final String USER_ID = "userID";
private static final String PROXY_SET = "proxy_enabled";
private static final String AUTH_SET = "proxy_auth_set";
private static final String PROXY_ADDR = "proxy_addr";
@ -84,25 +91,29 @@ public class GlobalSettings {
private static final String CUSTOM_CONSUMER_KEY_1 = "api_key1";
private static final String CUSTOM_CONSUMER_KEY_2 = "api_key2";
// login specific preference names
private static final String LOGGED_IN = "login";
private static final String CURRENT_ID = "userID";
private static final String CURRENT_AUTH_KEY1 = "key1";
private static final String CURRENT_AUTH_KEY2 = "key2";
// file name of the preferences
private static final String APP_SETTINGS = "settings";
// Default App settings
@IntRange(from = 0, to = 3)
@IntRange(from = 0, to = 4)
private static final int DEFAULT_FONT_INDEX = 0;
@IntRange(from = 0, to = 100)
private static final int DEFAULT_LIST_SIZE = 20;
private static final int DEFAULT_BACKGROUND_COLOR = 0xff0f114a;
private static final int DEFAULT_HIGHLIGHT_COLOR = 0xffff00ff;
private static final int DEFAULT_FONT_COLOR = 0xffffffff;
private static final int DEFAULT_FONT_COLOR = Color.WHITE;
private static final int DEFAULT_POPUP_COLOR = 0xff19aae8;
private static final int DEFAULT_CARD_COLOR = 0x40000000;
private static final int DEFAULT_ICON_COLOR = Color.WHITE;
private static final int DEFAULT_LOCATION_WOEID = 1;
private static final int DEFAULT_LOCATION_ID = 1;
private static final String DEFAULT_LOCATION_NAME = "Worldwide";
private static final GlobalSettings ourInstance = new GlobalSettings();
private SharedPreferences settings;
private TrendLocation location;
private String api_key1, api_key2;
@ -696,9 +707,9 @@ public class GlobalSettings {
Editor e = settings.edit();
e.putBoolean(LOGGED_IN, true);
e.putLong(USER_ID, userId);
e.putString(AUTH_KEY1, key1);
e.putString(AUTH_KEY2, key2);
e.putLong(CURRENT_ID, userId);
e.putString(CURRENT_AUTH_KEY1, key1);
e.putString(CURRENT_AUTH_KEY2, key2);
e.apply();
}
@ -773,17 +784,18 @@ public class GlobalSettings {
toolbarOverlap = settings.getBoolean(PROFILE_OVERLAP, true);
linkPreview = settings.getBoolean(LINK_PREVIEW, false);
customAPIKey = settings.getBoolean(CUSTOM_CONSUMER_KEY_SET, false);
api_key1 = settings.getString(CUSTOM_CONSUMER_KEY_1, "");
api_key2 = settings.getString(CUSTOM_CONSUMER_KEY_2, "");
auth_key1 = settings.getString(AUTH_KEY1, "");
auth_key2 = settings.getString(AUTH_KEY2, "");
proxyHost = settings.getString(PROXY_ADDR, "");
proxyPort = settings.getString(PROXY_PORT, "");
proxyUser = settings.getString(PROXY_USER, "");
proxyPass = settings.getString(PROXY_PASS, "");
userId = settings.getLong(USER_ID, 0);
String place = settings.getString(TREND_LOC, DEFAULT_LOCATION_NAME);
int woeId = settings.getInt(TREND_ID, DEFAULT_LOCATION_WOEID);
int woeId = settings.getInt(TREND_ID, DEFAULT_LOCATION_ID);
location = new TrendLocation(place, woeId);
api_key1 = settings.getString(CUSTOM_CONSUMER_KEY_1, "");
api_key2 = settings.getString(CUSTOM_CONSUMER_KEY_2, "");
auth_key1 = settings.getString(CURRENT_AUTH_KEY1, "");
auth_key2 = settings.getString(CURRENT_AUTH_KEY2, "");
userId = settings.getLong(CURRENT_ID, 0);
}
}

View File

@ -22,6 +22,7 @@ public class ConfirmDialog extends AlertDialog implements OnClickListener {
WRONG_PROXY,
DEL_DATABASE,
APP_LOG_OUT,
REMOVE_ACCOUNT,
TWEET_DELETE,
TWEET_EDITOR_LEAVE,
TWEET_EDITOR_ERROR,
@ -143,6 +144,12 @@ public class ConfirmDialog extends AlertDialog implements OnClickListener {
case LIST_DELETE:
message = c.getString(R.string.confirm_delete_list);
break;
case REMOVE_ACCOUNT:
message = c.getString(R.string.confirm_remove_account);
posButton = c.getString(R.string.dialog_button_ok);
negButton = c.getString(R.string.dialog_button_cancel);
break;
}
setTitle(title);
setMessage(message);

View File

@ -80,6 +80,7 @@ public class LinkDialog extends Dialog implements LinkPreviewCallback, OnClickLi
public void dismiss() {
super.dismiss();
textCrawler.cancel();
Picasso.get().cancelRequest(preview);
}

View File

@ -0,0 +1,132 @@
package org.nuclearfog.twidda.fragment;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import org.nuclearfog.twidda.adapter.AccountAdapter;
import org.nuclearfog.twidda.adapter.AccountAdapter.OnLoginClickListener;
import org.nuclearfog.twidda.backend.LoginLoader;
import org.nuclearfog.twidda.backend.engine.EngineException;
import org.nuclearfog.twidda.backend.model.Account;
import org.nuclearfog.twidda.backend.utils.ErrorHandler;
import org.nuclearfog.twidda.database.GlobalSettings;
import org.nuclearfog.twidda.dialog.ConfirmDialog;
import org.nuclearfog.twidda.dialog.ConfirmDialog.OnConfirmListener;
import java.util.List;
import static android.os.AsyncTask.Status.RUNNING;
import static org.nuclearfog.twidda.activity.AccountActivity.RET_ACCOUNT_CHANGE;
import static org.nuclearfog.twidda.dialog.ConfirmDialog.DialogType;
/**
* fragment class of the account manager
* all registered accounts are listed here
*
* @author nuclearfog
*/
public class AccountFragment extends ListFragment implements OnLoginClickListener, OnConfirmListener {
@Nullable
private LoginLoader loginTask;
private GlobalSettings settings;
private AccountAdapter adapter;
private AlertDialog dialog;
private Account selection;
@Override
protected void onCreate() {
dialog = new ConfirmDialog(requireContext(), DialogType.REMOVE_ACCOUNT, this);
settings = GlobalSettings.getInstance(requireContext());
}
@Override
public void onStart() {
super.onStart();
if (loginTask == null) {
setRefresh(true);
loginTask = new LoginLoader(this);
loginTask.execute();
}
}
@Override
public void onDestroy() {
if (loginTask != null && loginTask.getStatus() == RUNNING)
loginTask.cancel(true);
super.onDestroy();
}
@Override
protected void onReload() {
if (loginTask == null || loginTask.getStatus() != RUNNING)
loginTask = new LoginLoader(this);
loginTask.execute();
}
@Override
protected void onReset() {
adapter.clear();
loginTask = new LoginLoader(this);
loginTask.execute();
setRefresh(true);
}
@Override
protected AccountAdapter initAdapter() {
adapter = new AccountAdapter(settings, this);
return adapter;
}
@Override
public void onLoginClick(Account account) {
// set new account
String[] token = account.getKeys();
settings.setConnection(token[0], token[1], account.getId());
// finish activity and return to parent activity
requireActivity().setResult(RET_ACCOUNT_CHANGE);
requireActivity().finish();
}
@Override
public void onDeleteClick(Account account) {
if (!dialog.isShowing()) {
selection = account;
dialog.show();
}
}
@Override
public void onConfirm(DialogType type) {
loginTask = new LoginLoader(this);
loginTask.execute(selection);
}
/**
* called from {@link LoginLoader}
*
* @param result login information
*/
public void onSuccess(List<Account> result) {
adapter.setData(result);
setRefresh(false);
}
/**
* called from {@link LoginLoader} when an error occurs
*/
public void onError(EngineException err) {
ErrorHandler.handleFailure(requireContext(), err);
setRefresh(false);
}
}

View File

@ -11,6 +11,7 @@ import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.Adapter;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener;
@ -130,5 +131,5 @@ public abstract class ListFragment extends Fragment implements OnRefreshListener
*
* @return adapter for the recycler view list
*/
protected abstract Adapter<RecyclerView.ViewHolder> initAdapter();
protected abstract Adapter<? extends ViewHolder> initAdapter();
}

View File

@ -61,7 +61,7 @@ public class TrendFragment extends ListFragment implements TrendClickListener {
@Override
protected TrendAdapter initAdapter() {
adapter = new TrendAdapter(this);
adapter = new TrendAdapter(settings, this);
return adapter;
}

View File

@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportHeight="490.067"
android:viewportWidth="490.067">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M178.414,467.038c4.889,-27.122 16.416,-52.318 33.127,-73.795c-3.246,-3.229 -6.609,-6.334 -10.211,-9.164c-4.908,-3.884 -9.164,-8.431 -12.795,-13.421c-17.025,15.069 -37.912,24.081 -60.545,24.081c-22.631,0 -43.518,-9.012 -60.539,-24.081c-3.631,4.982 -7.875,9.522 -12.789,13.421c-22,17.324 -37.877,42.337 -43.154,71.603l-2.231,12.456c-0.973,5.427 0.508,11.017 4.051,15.247c3.541,4.24 8.779,6.682 14.301,6.682h152.525C177.785,482.715 177.018,474.878 178.414,467.038z" />
<path
android:fillColor="#FFFFFFFF"
android:pathData="M430.906,389.95c-4.061,-3.195 -7.611,-6.874 -10.92,-10.75c-20.691,19.073 -46.271,30.512 -74.031,30.512c-27.771,0 -53.348,-11.439 -74.049,-30.494c-3.297,3.858 -6.84,7.53 -10.9,10.732c-25.5,20.078 -43.877,49.043 -49.982,82.962c-0.76,4.253 0.404,8.623 3.178,11.931c2.768,3.31 6.869,5.225 11.189,5.225h7.807h233.291c4.32,0 8.422,-1.915 11.184,-5.225c2.779,-3.308 3.941,-7.678 3.184,-11.931C474.725,438.993 456.377,410.028 430.906,389.95z" />
<path
android:fillColor="#FFFFFFFF"
android:pathData="M255.035,256.895c0,66.111 40.736,119.685 90.92,119.685c50.178,0 90.873,-53.573 90.873,-119.685c0,-66.079 -40.695,-119.652 -90.873,-119.652C295.772,137.242 255.035,190.815 255.035,256.895z" />
<path
android:fillColor="#FFFFFFFF"
android:pathData="M127.99,361.606c38.68,0 70.018,-41.27 70.018,-92.148c0,-50.904 -31.338,-92.166 -70.018,-92.166c-38.625,0 -69.996,41.262 -69.996,92.166C57.994,320.337 89.365,361.606 127.99,361.606z" />
<path
android:fillColor="#FFFFFFFF"
android:pathData="M109.848,111.877l76.213,0.048c6.994,0.01 13.266,-4.221 15.934,-10.637c2.668,-6.446 1.191,-13.864 -3.752,-18.798l-16.426,-16.43c19.813,-10.677 42.082,-16.364 65.232,-16.364c36.814,0 71.43,14.334 97.459,40.364c4.853,4.854 11.209,7.28 17.568,7.28c6.355,0 12.717,-2.427 17.57,-7.28c9.707,-9.698 9.707,-25.431 0,-35.139C344.233,19.501 297.139,0 247.049,0c-36.545,0 -71.439,10.49 -101.434,29.862L122.02,6.268c-4.928,-4.934 -12.326,-6.408 -18.816,-3.747c-6.42,2.662 -10.609,8.925 -10.609,15.937v76.19C92.594,104.145 100.324,111.877 109.848,111.877z" />
</vector>

View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="@style/CardViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/login_layout_padding">
<ImageView
android:id="@+id/item_login_image"
android:layout_width="@dimen/login_image_size"
android:layout_height="@dimen/login_image_size"
android:layout_marginEnd="@dimen/login_layout_padding"
android:layout_marginRight="@dimen/login_layout_padding"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/item_login_image_barrier" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/item_login_image_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="start"
app:constraint_referenced_ids="item_login_username,item_login_screenname,item_login_createdAt" />
<TextView
android:id="@+id/item_login_username"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="@dimen/login_name_textsize"
app:layout_constraintStart_toEndOf="@id/item_login_image_barrier"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@id/item_login_button_barrier" />
<TextView
android:id="@+id/item_login_screenname"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="@dimen/login_name_textsize"
app:layout_constraintStart_toEndOf="@id/item_login_image_barrier"
app:layout_constraintTop_toBottomOf="@id/item_login_username"
app:layout_constraintBottom_toTopOf="@id/item_login_createdAt"
app:layout_constraintEnd_toStartOf="@id/item_login_button_barrier" />
<TextView
android:id="@+id/item_login_createdAt"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@id/item_login_image_barrier"
app:layout_constraintTop_toBottomOf="@id/item_login_screenname"
app:layout_constraintEnd_toStartOf="@id/item_login_button_barrier" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/item_login_button_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="end"
app:constraint_referenced_ids="item_login_username,item_login_screenname,item_login_createdAt" />
<ImageButton
android:id="@+id/item_login_remove"
android:layout_width="@dimen/login_cross_size"
android:layout_height="@dimen/login_cross_size"
android:layout_marginStart="@dimen/login_layout_padding"
android:layout_marginLeft="@dimen/login_layout_padding"
app:layout_constraintStart_toEndOf="@+id/item_login_button_barrier"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
style="@style/RoundButton" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

View File

@ -1,19 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/dm_layout"
android:id="@+id/fragment_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
android:orientation="vertical"
android:fitsSystemWindows="true">
<androidx.appcompat.widget.Toolbar
android:id="@+id/dm_toolbar"
android:id="@+id/fragment_toolbar"
android:layout_width="match_parent"
android:layout_height="@dimen/dmpage_toolbar_height" />
<androidx.viewpager.widget.ViewPager
android:id="@+id/dm_pager"
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>

View File

@ -274,7 +274,7 @@
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/tweet_pager" />
app:layout_constraintBottom_toTopOf="@id/tweet_reply_fragment" />
<Button
android:id="@+id/tweet_retweet"
@ -290,7 +290,7 @@
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="@+id/tweet_answer"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/tweet_pager" />
app:layout_constraintBottom_toTopOf="@id/tweet_reply_fragment" />
<Button
android:id="@+id/tweet_favorite"
@ -306,10 +306,10 @@
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="@+id/tweet_retweet"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/tweet_pager" />
app:layout_constraintBottom_toTopOf="@id/tweet_reply_fragment" />
<androidx.viewpager.widget.ViewPager
android:id="@+id/tweet_pager"
<androidx.fragment.app.FragmentContainerView
android:id="@+id/tweet_reply_fragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/user_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/user_toolbar"
android:layout_width="match_parent"
android:layout_height="@dimen/userlistpage_toolbar_height" />
<androidx.viewpager.widget.ViewPager
android:id="@+id/user_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_add_account"
android:icon="@drawable/add_user"
android:title="@string/menu_switch_account"
app:showAsAction="always" />
</menu>

View File

@ -22,6 +22,14 @@
android:visible="false"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="ifRoom|collapseActionView" />
<item
android:id="@+id/action_account"
android:icon="@drawable/switch_account"
android:title="@string/menu_open_home_profile"
android:visible="false"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_settings"
android:icon="@drawable/setting"

View File

@ -2,6 +2,12 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/login_select_account"
android:icon="@drawable/switch_account"
android:title="@string/menu_open_home_profile"
app:showAsAction="ifRoom" />
<item
android:id="@+id/login_setting"
android:icon="@drawable/setting"

View File

@ -9,7 +9,7 @@
app:showAsAction="always" />
<item
android:id="@+id/profile_follow"
android:icon="@drawable/follow"
android:icon="@drawable/add_user"
android:title="@string/menu_follow_user"
android:visible="false"
app:showAsAction="ifRoom" />

View File

@ -4,7 +4,7 @@
<item
android:id="@+id/menu_list_add_user"
android:icon="@drawable/follow"
android:icon="@drawable/add_user"
android:title="@string/menu_add_user"
android:visible="false"
app:actionViewClass="androidx.appcompat.widget.SearchView"

View File

@ -11,7 +11,7 @@
<string name="profile_image">Profilbild</string>
<string name="title_settings">Einstellungen</string>
<string name="settings_image">Bilder laden</string>
<string name="settings_clear_data">App Daten löschen</string>
<string name="settings_clear_data">Daten löschen</string>
<string name="toolbar_userlist_retweet">Tweet retweetet von</string>
<string name="settings_background">Hintergrund</string>
<string name="confirm_delete_tweet">Tweet löschen?</string>
@ -199,4 +199,6 @@
<string name="dialog_link_image_preview">Linkvorschau Bild</string>
<string name="dialog_link_close">Linkvorschau schließen</string>
<string name="settings_toggle_link_preview">Linkvorschau aktivieren</string>
<string name="confirm_remove_account">Account aus der Liste entfernen?</string>
<string name="menu_switch_account">Account auswählen</string>
</resources>

View File

@ -165,7 +165,6 @@
<!--dimens of item_image_load.xml-->
<!--dimens of page_userlist.xml-->
<dimen name="userlistpage_toolbar_height">@dimen/toolbar_height</dimen>
<!--dimens of page_search.xml-->
<dimen name="searchpage_toolbar_height">@dimen/toolbar_height</dimen>
@ -210,4 +209,10 @@
<dimen name="dialog_linkpreview_margin">4dp</dimen>
<dimen name="dialog_linkpreview_padding">5dp</dimen>
<!--dimens of item_login.xml-->
<dimen name="login_name_textsize">16sp</dimen>
<dimen name="login_layout_padding">8dp</dimen>
<dimen name="login_cross_size">36dp</dimen>
<dimen name="login_image_size">64dp</dimen>
</resources>

View File

@ -222,5 +222,8 @@
<string name="dialog_button_ok">OK</string>
<string name="dialog_link_image_preview">Link preview image</string>
<string name="dialog_link_close">close link preview</string>
<string name="account_page" translatable="false">Accounts</string>
<string name="confirm_remove_account">Remove account from list?</string>
<string name="menu_switch_account">select account</string>
</resources>