Account activity redesign (#662)
* Refactor-all-the-things version of the fix for issue #573 * Migrate SpanUtils to kotlin because why not * Minimal fix for issue #573 * Add tests for compose spanning * Clean up code suggestions * Make FakeSpannable.getSpans implementation less awkward * Add secondary validation pass for urls * Address code review feedback * Fixup type filtering in FakeSpannable again * Make all mentions in compose activity use the default link color * new layout for AccountActivity * fix the light theme * convert AccountActivity to Kotlin * introduce AccountViewModel * Merge branch 'master' into account-activity-redesign # Conflicts: # app/src/main/java/com/keylesspalace/tusky/AccountActivity.java * add Bot badge to profile * parse custom emojis in usernames * add possibility to cancel follow request * add third tab on profiles * add account fields to profile * add support for moved accounts * set click listener on account moved view * fix tests * use 24dp as statusbar size * add ability to hide reblogs from followed accounts * add button to edit own account to AccountActivity * set toolbar top margin programmatically * fix crash * add shadow behind statusbar * introduce ViewExtensions to clean up code * move code out of offsetChangedListener for perf reasons * clean up stuff * add error handling * improve type safety * fix ConstraintLayout warning * remove unneeded ressources * fix event dispatching * fix crash in event handling * set correct emoji on title * improve some things * wrap follower/foillowing/status views
This commit is contained in:
parent
c450af7b0d
commit
63f9d99390
60 changed files with 1422 additions and 978 deletions
|
@ -75,6 +75,8 @@ dependencies {
|
|||
implementation "com.android.support:support-emoji:$supportLibraryVersion"
|
||||
implementation "com.android.support:support-emoji-appcompat:$supportLibraryVersion"
|
||||
implementation "de.c1710:filemojicompat:1.0.5"
|
||||
// architecture components
|
||||
implementation 'android.arch.lifecycle:extensions:1.1.1'
|
||||
//room
|
||||
implementation 'android.arch.persistence.room:runtime:1.1.0'
|
||||
kapt 'android.arch.persistence.room:compiler:1.1.0'
|
||||
|
|
|
@ -69,8 +69,7 @@ public class AboutActivity extends BaseActivity implements Injectable {
|
|||
}
|
||||
|
||||
private void viewAccount(String id) {
|
||||
Intent intent = new Intent(this, AccountActivity.class);
|
||||
intent.putExtra("id", id);
|
||||
Intent intent = AccountActivity.getIntent(this, id);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,717 +0,0 @@
|
|||
/* Copyright 2017 Andrew Dawson
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.AttrRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.AppBarLayout;
|
||||
import android.support.design.widget.CollapsingToolbarLayout;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.design.widget.TabLayout;
|
||||
import android.support.text.emoji.EmojiCompat;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.keylesspalace.tusky.appstore.EventHub;
|
||||
import com.keylesspalace.tusky.appstore.BlockEvent;
|
||||
import com.keylesspalace.tusky.appstore.MuteEvent;
|
||||
import com.keylesspalace.tusky.appstore.UnfollowEvent;
|
||||
import com.keylesspalace.tusky.db.AccountEntity;
|
||||
import com.keylesspalace.tusky.entity.Account;
|
||||
import com.keylesspalace.tusky.entity.Relationship;
|
||||
import com.keylesspalace.tusky.interfaces.ActionButtonActivity;
|
||||
import com.keylesspalace.tusky.interfaces.LinkListener;
|
||||
import com.keylesspalace.tusky.pager.AccountPagerAdapter;
|
||||
import com.keylesspalace.tusky.util.Assert;
|
||||
import com.keylesspalace.tusky.util.LinkHelper;
|
||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
import com.pkmmte.view.CircularImageView;
|
||||
import com.squareup.picasso.Picasso;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.android.AndroidInjector;
|
||||
import dagger.android.DispatchingAndroidInjector;
|
||||
import dagger.android.support.HasSupportFragmentInjector;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public final class AccountActivity extends BottomSheetActivity implements ActionButtonActivity,
|
||||
HasSupportFragmentInjector {
|
||||
private static final String TAG = "AccountActivity"; // logging tag
|
||||
|
||||
private enum FollowState {
|
||||
NOT_FOLLOWING,
|
||||
FOLLOWING,
|
||||
REQUESTED,
|
||||
}
|
||||
|
||||
@Inject
|
||||
public DispatchingAndroidInjector<Fragment> dispatchingAndroidInjector;
|
||||
@Inject
|
||||
public EventHub appstore;
|
||||
|
||||
private String accountId;
|
||||
private FollowState followState;
|
||||
private boolean blocking;
|
||||
private boolean muting;
|
||||
private boolean isSelf;
|
||||
private Account loadedAccount;
|
||||
|
||||
private CircularImageView avatar;
|
||||
private ImageView header;
|
||||
private FloatingActionButton floatingBtn;
|
||||
private Button followBtn;
|
||||
private TextView followsYouView;
|
||||
private TabLayout tabLayout;
|
||||
private ImageView accountLockedView;
|
||||
private View container;
|
||||
private TextView followersTextView;
|
||||
private TextView followingTextView;
|
||||
private TextView statusesTextView;
|
||||
|
||||
private boolean hideFab;
|
||||
private int oldOffset;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_account);
|
||||
|
||||
avatar = findViewById(R.id.account_avatar);
|
||||
header = findViewById(R.id.account_header);
|
||||
floatingBtn = findViewById(R.id.floating_btn);
|
||||
followBtn = findViewById(R.id.follow_btn);
|
||||
followsYouView = findViewById(R.id.account_follows_you);
|
||||
tabLayout = findViewById(R.id.tab_layout);
|
||||
accountLockedView = findViewById(R.id.account_locked);
|
||||
container = findViewById(R.id.activity_account);
|
||||
followersTextView = findViewById(R.id.followers_tv);
|
||||
followingTextView = findViewById(R.id.following_tv);
|
||||
statusesTextView = findViewById(R.id.statuses_btn);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
accountId = savedInstanceState.getString("accountId");
|
||||
followState = (FollowState) savedInstanceState.getSerializable("followState");
|
||||
blocking = savedInstanceState.getBoolean("blocking");
|
||||
muting = savedInstanceState.getBoolean("muting");
|
||||
} else {
|
||||
Intent intent = getIntent();
|
||||
accountId = intent.getStringExtra("id");
|
||||
followState = FollowState.NOT_FOLLOWING;
|
||||
blocking = false;
|
||||
muting = false;
|
||||
}
|
||||
loadedAccount = null;
|
||||
|
||||
// Setup the toolbar.
|
||||
final Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setTitle(null);
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
actionBar.setDisplayShowHomeEnabled(true);
|
||||
}
|
||||
|
||||
hideFab = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("fabHide", false);
|
||||
|
||||
// Add a listener to change the toolbar icon color when it enters/exits its collapsed state.
|
||||
AppBarLayout appBarLayout = findViewById(R.id.account_app_bar_layout);
|
||||
final CollapsingToolbarLayout collapsingToolbar = findViewById(R.id.collapsing_toolbar);
|
||||
appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
|
||||
@AttrRes
|
||||
int priorAttribute = R.attr.account_toolbar_icon_tint_uncollapsed;
|
||||
|
||||
@Override
|
||||
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
|
||||
@AttrRes int attribute;
|
||||
if (collapsingToolbar.getHeight() + verticalOffset
|
||||
< 2 * ViewCompat.getMinimumHeight(collapsingToolbar)) {
|
||||
|
||||
toolbar.setTitleTextColor(ThemeUtils.getColor(AccountActivity.this,
|
||||
android.R.attr.textColorPrimary));
|
||||
toolbar.setSubtitleTextColor(ThemeUtils.getColor(AccountActivity.this,
|
||||
android.R.attr.textColorSecondary));
|
||||
|
||||
attribute = R.attr.account_toolbar_icon_tint_collapsed;
|
||||
} else {
|
||||
toolbar.setTitleTextColor(Color.TRANSPARENT);
|
||||
toolbar.setSubtitleTextColor(Color.TRANSPARENT);
|
||||
|
||||
attribute = R.attr.account_toolbar_icon_tint_uncollapsed;
|
||||
}
|
||||
if (attribute != priorAttribute) {
|
||||
priorAttribute = attribute;
|
||||
Context context = toolbar.getContext();
|
||||
ThemeUtils.setDrawableTint(context, toolbar.getNavigationIcon(), attribute);
|
||||
ThemeUtils.setDrawableTint(context, toolbar.getOverflowIcon(), attribute);
|
||||
}
|
||||
|
||||
if (floatingBtn != null && hideFab && !isSelf && !blocking) {
|
||||
if (verticalOffset > oldOffset) {
|
||||
floatingBtn.show();
|
||||
}
|
||||
if (verticalOffset < oldOffset) {
|
||||
floatingBtn.hide();
|
||||
}
|
||||
}
|
||||
oldOffset = verticalOffset;
|
||||
}
|
||||
});
|
||||
|
||||
// Initialise the default UI states.
|
||||
floatingBtn.hide();
|
||||
followBtn.setVisibility(View.GONE);
|
||||
followsYouView.setVisibility(View.GONE);
|
||||
|
||||
// Obtain information to fill out the profile.
|
||||
obtainAccount();
|
||||
|
||||
AccountEntity activeAccount = accountManager.getActiveAccount();
|
||||
|
||||
if (accountId.equals(activeAccount.getAccountId())) {
|
||||
isSelf = true;
|
||||
} else {
|
||||
isSelf = false;
|
||||
obtainRelationships();
|
||||
}
|
||||
|
||||
// Setup the tabs and timeline pager.
|
||||
AccountPagerAdapter adapter = new AccountPagerAdapter(getSupportFragmentManager(),
|
||||
accountId);
|
||||
String[] pageTitles = {
|
||||
getString(R.string.title_statuses),
|
||||
getString(R.string.title_media)
|
||||
};
|
||||
adapter.setPageTitles(pageTitles);
|
||||
final ViewPager viewPager = findViewById(R.id.pager);
|
||||
int pageMargin = getResources().getDimensionPixelSize(R.dimen.tab_page_margin);
|
||||
viewPager.setPageMargin(pageMargin);
|
||||
Drawable pageMarginDrawable = ThemeUtils.getDrawable(this, R.attr.tab_page_margin_drawable,
|
||||
R.drawable.tab_page_margin_dark);
|
||||
viewPager.setPageMarginDrawable(pageMarginDrawable);
|
||||
viewPager.setAdapter(adapter);
|
||||
viewPager.setOffscreenPageLimit(0);
|
||||
tabLayout.setupWithViewPager(viewPager);
|
||||
|
||||
View.OnClickListener accountListClickListener = v -> {
|
||||
AccountListActivity.Type type;
|
||||
switch (v.getId()) {
|
||||
case R.id.followers_tv:
|
||||
type = AccountListActivity.Type.FOLLOWERS;
|
||||
break;
|
||||
case R.id.following_tv:
|
||||
type = AccountListActivity.Type.FOLLOWING;
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
Intent intent = AccountListActivity.newIntent(AccountActivity.this, type,
|
||||
accountId);
|
||||
startActivity(intent);
|
||||
};
|
||||
followersTextView.setOnClickListener(accountListClickListener);
|
||||
followingTextView.setOnClickListener(accountListClickListener);
|
||||
|
||||
statusesTextView.setOnClickListener(v -> {
|
||||
// Make nice ripple effect on tab
|
||||
|
||||
//noinspection ConstantConditions
|
||||
tabLayout.getTabAt(0).select();
|
||||
final View poorTabView = ((ViewGroup) tabLayout.getChildAt(0)).getChildAt(0);
|
||||
poorTabView.setPressed(true);
|
||||
tabLayout.postDelayed(() -> poorTabView.setPressed(false), 300);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
outState.putString("accountId", accountId);
|
||||
outState.putSerializable("followState", followState);
|
||||
outState.putBoolean("blocking", blocking);
|
||||
outState.putBoolean("muting", muting);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
private void obtainAccount() {
|
||||
mastodonApi.account(accountId).enqueue(new Callback<Account>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<Account> call,
|
||||
@NonNull Response<Account> response) {
|
||||
if (response.isSuccessful()) {
|
||||
onObtainAccountSuccess(response.body());
|
||||
} else {
|
||||
onObtainAccountFailure();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<Account> call, @NonNull Throwable t) {
|
||||
onObtainAccountFailure();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onObtainAccountSuccess(Account account) {
|
||||
loadedAccount = account;
|
||||
|
||||
TextView username = findViewById(R.id.account_username);
|
||||
TextView displayName = findViewById(R.id.account_display_name);
|
||||
TextView note = findViewById(R.id.account_note);
|
||||
|
||||
String usernameFormatted = String.format(
|
||||
getString(R.string.status_username_format), account.getUsername());
|
||||
username.setText(usernameFormatted);
|
||||
|
||||
displayName.setText(account.getName());
|
||||
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setTitle(EmojiCompat.get().process(account.getName()));
|
||||
|
||||
String subtitle = String.format(getString(R.string.status_username_format),
|
||||
account.getUsername());
|
||||
getSupportActionBar().setSubtitle(subtitle);
|
||||
}
|
||||
|
||||
LinkHelper.setClickableText(note, account.getNote(), null, new LinkListener() {
|
||||
@Override
|
||||
public void onViewTag(String tag) {
|
||||
Intent intent = new Intent(AccountActivity.this, ViewTagActivity.class);
|
||||
intent.putExtra("hashtag", tag);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewAccount(String id) {
|
||||
Intent intent = new Intent(AccountActivity.this, AccountActivity.class);
|
||||
intent.putExtra("id", id);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewUrl(String url) {
|
||||
viewUrl(url);
|
||||
}
|
||||
});
|
||||
|
||||
if (account.getLocked()) {
|
||||
accountLockedView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
accountLockedView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
Picasso.with(this)
|
||||
.load(account.getAvatar())
|
||||
.placeholder(R.drawable.avatar_default)
|
||||
.into(avatar);
|
||||
Picasso.with(this)
|
||||
.load(account.getHeader())
|
||||
.placeholder(R.drawable.account_header_default)
|
||||
.into(header);
|
||||
|
||||
NumberFormat numberFormat = NumberFormat.getNumberInstance();
|
||||
|
||||
String followersCount = numberFormat.format(account.getFollowersCount());
|
||||
String followingCount = numberFormat.format(account.getFollowingCount());
|
||||
String statusesCount = numberFormat.format(account.getStatusesCount());
|
||||
followersTextView.setText(getString(R.string.title_x_followers, followersCount));
|
||||
followingTextView.setText(getString(R.string.title_x_following, followingCount));
|
||||
statusesTextView.setText(getString(R.string.title_x_statuses, statusesCount));
|
||||
|
||||
}
|
||||
|
||||
private void onObtainAccountFailure() {
|
||||
Snackbar.make(tabLayout, R.string.error_generic, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.action_retry, v -> obtainAccount())
|
||||
.show();
|
||||
}
|
||||
|
||||
private void obtainRelationships() {
|
||||
List<String> ids = new ArrayList<>(1);
|
||||
ids.add(accountId);
|
||||
mastodonApi.relationships(ids).enqueue(new Callback<List<Relationship>>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<List<Relationship>> call,
|
||||
@NonNull Response<List<Relationship>> response) {
|
||||
List<Relationship> relationships = response.body();
|
||||
if (response.isSuccessful() && relationships != null) {
|
||||
Relationship relationship = relationships.get(0);
|
||||
onObtainRelationshipsSuccess(relationship);
|
||||
} else {
|
||||
onObtainRelationshipsFailure(new Exception(response.message()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<List<Relationship>> call, @NonNull Throwable t) {
|
||||
onObtainRelationshipsFailure((Exception) t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onObtainRelationshipsSuccess(Relationship relation) {
|
||||
if (relation.getFollowing()) {
|
||||
followState = FollowState.FOLLOWING;
|
||||
} else if (relation.getRequested()) {
|
||||
followState = FollowState.REQUESTED;
|
||||
} else {
|
||||
followState = FollowState.NOT_FOLLOWING;
|
||||
}
|
||||
this.blocking = relation.getBlocking();
|
||||
this.muting = relation.getMuting();
|
||||
|
||||
if (relation.getFollowedBy()) {
|
||||
followsYouView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
followsYouView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
private void updateFollowButton(Button button) {
|
||||
switch (followState) {
|
||||
case NOT_FOLLOWING: {
|
||||
button.setText(R.string.action_follow);
|
||||
break;
|
||||
}
|
||||
case REQUESTED: {
|
||||
button.setText(R.string.state_follow_requested);
|
||||
break;
|
||||
}
|
||||
case FOLLOWING: {
|
||||
button.setText(R.string.action_unfollow);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateButtons() {
|
||||
invalidateOptionsMenu();
|
||||
|
||||
if (!isSelf && !blocking) {
|
||||
floatingBtn.show();
|
||||
followBtn.setVisibility(View.VISIBLE);
|
||||
|
||||
updateFollowButton(followBtn);
|
||||
|
||||
floatingBtn.setOnClickListener(v -> mention());
|
||||
|
||||
followBtn.setOnClickListener(v -> {
|
||||
switch (followState) {
|
||||
case NOT_FOLLOWING: {
|
||||
follow(accountId);
|
||||
break;
|
||||
}
|
||||
case REQUESTED: {
|
||||
showFollowRequestPendingDialog();
|
||||
break;
|
||||
}
|
||||
case FOLLOWING: {
|
||||
showUnfollowWarningDialog();
|
||||
break;
|
||||
}
|
||||
}
|
||||
updateFollowButton(followBtn);
|
||||
});
|
||||
} else {
|
||||
floatingBtn.hide();
|
||||
followBtn.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void onObtainRelationshipsFailure(Exception exception) {
|
||||
Log.e(TAG, "Could not obtain relationships. " + exception.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.account_toolbar, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
private String getFollowAction() {
|
||||
switch (followState) {
|
||||
default:
|
||||
case NOT_FOLLOWING:
|
||||
return getString(R.string.action_follow);
|
||||
case REQUESTED:
|
||||
case FOLLOWING:
|
||||
return getString(R.string.action_unfollow);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
if (!isSelf) {
|
||||
MenuItem follow = menu.findItem(R.id.action_follow);
|
||||
follow.setTitle(getFollowAction());
|
||||
follow.setVisible(followState != FollowState.REQUESTED);
|
||||
|
||||
MenuItem block = menu.findItem(R.id.action_block);
|
||||
String title;
|
||||
if (blocking) {
|
||||
title = getString(R.string.action_unblock);
|
||||
} else {
|
||||
title = getString(R.string.action_block);
|
||||
}
|
||||
block.setTitle(title);
|
||||
MenuItem mute = menu.findItem(R.id.action_mute);
|
||||
if (muting) {
|
||||
title = getString(R.string.action_unmute);
|
||||
} else {
|
||||
title = getString(R.string.action_mute);
|
||||
}
|
||||
mute.setTitle(title);
|
||||
} else {
|
||||
// It shouldn't be possible to block or follow yourself.
|
||||
menu.removeItem(R.id.action_follow);
|
||||
menu.removeItem(R.id.action_block);
|
||||
menu.removeItem(R.id.action_mute);
|
||||
}
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
private void follow(final String id) {
|
||||
Callback<Relationship> cb = new Callback<Relationship>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<Relationship> call,
|
||||
@NonNull Response<Relationship> response) {
|
||||
Relationship relationship = response.body();
|
||||
if (response.isSuccessful() && relationship != null) {
|
||||
if (relationship.getFollowing()) {
|
||||
followState = FollowState.FOLLOWING;
|
||||
} else if (relationship.getRequested()) {
|
||||
followState = FollowState.REQUESTED;
|
||||
Snackbar.make(container, R.string.state_follow_requested,
|
||||
Snackbar.LENGTH_LONG).show();
|
||||
} else {
|
||||
followState = FollowState.NOT_FOLLOWING;
|
||||
appstore.dispatch(new UnfollowEvent(id));
|
||||
}
|
||||
updateButtons();
|
||||
} else {
|
||||
onFollowFailure(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<Relationship> call, @NonNull Throwable t) {
|
||||
onFollowFailure(id);
|
||||
}
|
||||
};
|
||||
|
||||
Assert.expect(followState != FollowState.REQUESTED);
|
||||
switch (followState) {
|
||||
case NOT_FOLLOWING: {
|
||||
mastodonApi.followAccount(id).enqueue(cb);
|
||||
break;
|
||||
}
|
||||
case FOLLOWING: {
|
||||
mastodonApi.unfollowAccount(id).enqueue(cb);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onFollowFailure(final String id) {
|
||||
View.OnClickListener listener = v -> follow(id);
|
||||
Snackbar.make(container, R.string.error_generic, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.action_retry, listener)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showFollowRequestPendingDialog() {
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(R.string.dialog_message_follow_request)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showUnfollowWarningDialog() {
|
||||
DialogInterface.OnClickListener unfollowListener = (dialogInterface, i) -> follow(accountId);
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(R.string.dialog_unfollow_warning)
|
||||
.setPositiveButton(android.R.string.ok, unfollowListener)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void block(final String id) {
|
||||
Callback<Relationship> cb = new Callback<Relationship>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<Relationship> call,
|
||||
@NonNull Response<Relationship> response) {
|
||||
Relationship relationship = response.body();
|
||||
if (response.isSuccessful() && relationship != null) {
|
||||
appstore.dispatch(new BlockEvent(id));
|
||||
blocking = relationship.getBlocking();
|
||||
updateButtons();
|
||||
} else {
|
||||
onBlockFailure(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<Relationship> call, @NonNull Throwable t) {
|
||||
onBlockFailure(id);
|
||||
}
|
||||
};
|
||||
if (blocking) {
|
||||
mastodonApi.unblockAccount(id).enqueue(cb);
|
||||
} else {
|
||||
mastodonApi.blockAccount(id).enqueue(cb);
|
||||
}
|
||||
}
|
||||
|
||||
private void onBlockFailure(final String id) {
|
||||
View.OnClickListener listener = v -> block(id);
|
||||
Snackbar.make(container, R.string.error_generic, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.action_retry, listener)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void mute(final String id) {
|
||||
Callback<Relationship> cb = new Callback<Relationship>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<Relationship> call,
|
||||
@NonNull Response<Relationship> response) {
|
||||
Relationship relationship = response.body();
|
||||
if (response.isSuccessful() && relationship != null) {
|
||||
appstore.dispatch(new MuteEvent(id));
|
||||
muting = relationship.getMuting();
|
||||
updateButtons();
|
||||
} else {
|
||||
onMuteFailure(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<Relationship> call, @NonNull Throwable t) {
|
||||
onMuteFailure(id);
|
||||
}
|
||||
};
|
||||
|
||||
if (muting) {
|
||||
mastodonApi.unmuteAccount(id).enqueue(cb);
|
||||
} else {
|
||||
mastodonApi.muteAccount(id).enqueue(cb);
|
||||
}
|
||||
}
|
||||
|
||||
private void onMuteFailure(final String id) {
|
||||
View.OnClickListener listener = v -> mute(id);
|
||||
Snackbar.make(container, R.string.error_generic, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.action_retry, listener)
|
||||
.show();
|
||||
}
|
||||
|
||||
private boolean mention() {
|
||||
if (loadedAccount == null) {
|
||||
// If the account isn't loaded yet, eat the input.
|
||||
return false;
|
||||
}
|
||||
Intent intent = new ComposeActivity.IntentBuilder()
|
||||
.mentionedUsernames(Collections.singleton(loadedAccount.getUsername()))
|
||||
.build(this);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void broadcast(String action, String id) {
|
||||
Intent intent = new Intent(action);
|
||||
intent.putExtra("id", id);
|
||||
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home: {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
case R.id.action_mention: {
|
||||
return mention();
|
||||
}
|
||||
case R.id.action_open_in_web: {
|
||||
if (loadedAccount == null) {
|
||||
// If the account isn't loaded yet, eat the input.
|
||||
return false;
|
||||
}
|
||||
LinkHelper.openLink(loadedAccount.getUrl(), this);
|
||||
return true;
|
||||
}
|
||||
case R.id.action_follow: {
|
||||
follow(accountId);
|
||||
return true;
|
||||
}
|
||||
case R.id.action_block: {
|
||||
block(accountId);
|
||||
return true;
|
||||
}
|
||||
case R.id.action_mute: {
|
||||
mute(accountId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public FloatingActionButton getActionButton() {
|
||||
if (!isSelf && !blocking) {
|
||||
return floatingBtn;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AndroidInjector<Fragment> supportFragmentInjector() {
|
||||
return dispatchingAndroidInjector;
|
||||
}
|
||||
|
||||
}
|
620
app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt
Normal file
620
app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt
Normal file
|
@ -0,0 +1,620 @@
|
|||
/* Copyright 2018 Conny Duck
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky
|
||||
|
||||
import android.animation.ArgbEvaluator
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.arch.lifecycle.Observer
|
||||
import android.arch.lifecycle.ViewModelProviders
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.graphics.PorterDuff
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.annotation.AttrRes
|
||||
import android.support.annotation.ColorInt
|
||||
import android.support.annotation.Px
|
||||
import android.support.design.widget.*
|
||||
import android.support.text.emoji.EmojiCompat
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.support.v4.view.ViewCompat
|
||||
import android.support.v4.widget.TextViewCompat
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.keylesspalace.tusky.adapter.AccountFieldAdapter
|
||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
import com.keylesspalace.tusky.entity.Account
|
||||
import com.keylesspalace.tusky.entity.Relationship
|
||||
import com.keylesspalace.tusky.interfaces.ActionButtonActivity
|
||||
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||
import com.keylesspalace.tusky.pager.AccountPagerAdapter
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import com.keylesspalace.tusky.view.RoundedTransformation
|
||||
import com.keylesspalace.tusky.viewmodel.AccountViewModel
|
||||
import com.squareup.picasso.Picasso
|
||||
import dagger.android.AndroidInjector
|
||||
import dagger.android.DispatchingAndroidInjector
|
||||
import dagger.android.support.HasSupportFragmentInjector
|
||||
import kotlinx.android.synthetic.main.activity_account.*
|
||||
import kotlinx.android.synthetic.main.view_account_moved.*
|
||||
import java.text.NumberFormat
|
||||
import javax.inject.Inject
|
||||
|
||||
class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportFragmentInjector, LinkListener {
|
||||
|
||||
@Inject
|
||||
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelFactory
|
||||
|
||||
private lateinit var viewModel: AccountViewModel
|
||||
|
||||
private val accountFieldAdapter = AccountFieldAdapter(this)
|
||||
|
||||
private lateinit var accountId: String
|
||||
private var followState: FollowState? = null
|
||||
private var blocking: Boolean = false
|
||||
private var muting: Boolean = false
|
||||
private var showingReblogs: Boolean = false
|
||||
private var isSelf: Boolean = false
|
||||
private var loadedAccount: Account? = null
|
||||
|
||||
// fields for scroll animation
|
||||
private var hideFab: Boolean = false
|
||||
private var oldOffset: Int = 0
|
||||
@ColorInt
|
||||
private var toolbarColor: Int = 0
|
||||
@ColorInt
|
||||
private var backgroundColor: Int = 0
|
||||
@ColorInt
|
||||
private var statusBarColorTransparent: Int = 0
|
||||
@ColorInt
|
||||
private var statusBarColorOpaque: Int = 0
|
||||
@ColorInt
|
||||
private var textColorPrimary: Int = 0
|
||||
@ColorInt
|
||||
private var textColorSecondary: Int = 0
|
||||
@Px
|
||||
private var avatarSize: Float = 0f
|
||||
@Px
|
||||
private var titleVisibleHeight: Int = 0
|
||||
|
||||
private enum class FollowState {
|
||||
NOT_FOLLOWING,
|
||||
FOLLOWING,
|
||||
REQUESTED
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
viewModel = ViewModelProviders.of(this, viewModelFactory)[AccountViewModel::class.java]
|
||||
|
||||
viewModel.accountData.observe(this, Observer<Resource<Account>> {
|
||||
when (it) {
|
||||
is Success -> onAccountChanged(it.data)
|
||||
is Error -> {
|
||||
Snackbar.make(accountCoordinatorLayout, R.string.error_generic, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.action_retry) { reload() }
|
||||
.show()
|
||||
}
|
||||
}
|
||||
})
|
||||
viewModel.relationshipData.observe(this, Observer<Resource<Relationship>> {
|
||||
val relation = it?.data
|
||||
if (relation != null) {
|
||||
onRelationshipChanged(relation)
|
||||
}
|
||||
|
||||
if (it is Error) {
|
||||
Snackbar.make(accountCoordinatorLayout, R.string.error_generic, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.action_retry) { reload() }
|
||||
.show()
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
val decorView = window.decorView
|
||||
decorView.systemUiVisibility = decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
window.statusBarColor = Color.TRANSPARENT
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_account)
|
||||
|
||||
val intent = intent
|
||||
accountId = intent.getStringExtra(KEY_ACCOUNT_ID)
|
||||
followState = FollowState.NOT_FOLLOWING
|
||||
blocking = false
|
||||
muting = false
|
||||
|
||||
loadedAccount = null
|
||||
|
||||
// set toolbar top margin according to system window insets
|
||||
ViewCompat.setOnApplyWindowInsetsListener(accountCoordinatorLayout) { _, insets ->
|
||||
val top = insets.systemWindowInsetTop
|
||||
|
||||
val toolbarParams = accountToolbar.layoutParams as CollapsingToolbarLayout.LayoutParams
|
||||
toolbarParams.topMargin = top
|
||||
|
||||
insets.consumeSystemWindowInsets()
|
||||
}
|
||||
|
||||
// Setup the toolbar.
|
||||
setSupportActionBar(accountToolbar)
|
||||
supportActionBar?.title = null
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||
|
||||
hideFab = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("fabHide", false)
|
||||
|
||||
toolbarColor = ThemeUtils.getColor(this, R.attr.toolbar_background_color)
|
||||
backgroundColor = ThemeUtils.getColor(this, android.R.attr.colorBackground)
|
||||
statusBarColorTransparent = ContextCompat.getColor(this, R.color.header_background_filter)
|
||||
statusBarColorOpaque = ThemeUtils.getColor(this, R.attr.colorPrimaryDark)
|
||||
textColorPrimary = ThemeUtils.getColor(this, android.R.attr.textColorPrimary)
|
||||
textColorSecondary = ThemeUtils.getColor(this, android.R.attr.textColorSecondary)
|
||||
avatarSize = resources.getDimensionPixelSize(R.dimen.account_activity_avatar_size).toFloat()
|
||||
titleVisibleHeight = resources.getDimensionPixelSize(R.dimen.account_activity_scroll_title_visible_height)
|
||||
|
||||
ThemeUtils.setDrawableTint(this, accountToolbar.navigationIcon, R.attr.account_toolbar_icon_tint_uncollapsed)
|
||||
ThemeUtils.setDrawableTint(this, accountToolbar.overflowIcon, R.attr.account_toolbar_icon_tint_uncollapsed)
|
||||
|
||||
// Add a listener to change the toolbar icon color when it enters/exits its collapsed state.
|
||||
accountAppBarLayout.addOnOffsetChangedListener(object : AppBarLayout.OnOffsetChangedListener {
|
||||
@AttrRes var priorAttribute = R.attr.account_toolbar_icon_tint_uncollapsed
|
||||
|
||||
override fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) {
|
||||
|
||||
@AttrRes val attribute = if (titleVisibleHeight + verticalOffset < 0) {
|
||||
accountToolbar.setTitleTextColor(textColorPrimary)
|
||||
accountToolbar.setSubtitleTextColor(textColorSecondary)
|
||||
|
||||
R.attr.account_toolbar_icon_tint_collapsed
|
||||
} else {
|
||||
accountToolbar.setTitleTextColor(Color.TRANSPARENT)
|
||||
accountToolbar.setSubtitleTextColor(Color.TRANSPARENT)
|
||||
|
||||
R.attr.account_toolbar_icon_tint_uncollapsed
|
||||
}
|
||||
if (attribute != priorAttribute) {
|
||||
priorAttribute = attribute
|
||||
val context = accountToolbar.context
|
||||
ThemeUtils.setDrawableTint(context, accountToolbar.navigationIcon, attribute)
|
||||
ThemeUtils.setDrawableTint(context, accountToolbar.overflowIcon, attribute)
|
||||
}
|
||||
|
||||
if (hideFab && !isSelf && !blocking) {
|
||||
if (verticalOffset > oldOffset) {
|
||||
accountFloatingActionButton.show()
|
||||
}
|
||||
if (verticalOffset < oldOffset) {
|
||||
accountFloatingActionButton.hide()
|
||||
}
|
||||
}
|
||||
oldOffset = verticalOffset
|
||||
|
||||
val scaledAvatarSize = (avatarSize + verticalOffset) / avatarSize
|
||||
|
||||
accountAvatarImageView.scaleX = scaledAvatarSize
|
||||
accountAvatarImageView.scaleY = scaledAvatarSize
|
||||
|
||||
accountAvatarImageView.visible(scaledAvatarSize > 0)
|
||||
|
||||
var transparencyPercent = Math.abs(verticalOffset) / titleVisibleHeight.toFloat()
|
||||
if (transparencyPercent > 1) transparencyPercent = 1f
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
window.statusBarColor = argbEvaluator.evaluate(transparencyPercent, statusBarColorTransparent, statusBarColorOpaque) as Int
|
||||
}
|
||||
|
||||
val evaluatedToolbarColor = argbEvaluator.evaluate(transparencyPercent, Color.TRANSPARENT, toolbarColor) as Int
|
||||
val evaluatedTabBarColor = argbEvaluator.evaluate(transparencyPercent, backgroundColor, toolbarColor) as Int
|
||||
accountToolbar.setBackgroundColor(evaluatedToolbarColor)
|
||||
accountHeaderInfoContainer.setBackgroundColor(evaluatedTabBarColor)
|
||||
accountTabLayout.setBackgroundColor(evaluatedTabBarColor)
|
||||
}
|
||||
})
|
||||
|
||||
// Initialise the default UI states.
|
||||
accountFloatingActionButton.hide()
|
||||
accountFollowButton.hide()
|
||||
accountFollowsYouTextView.hide()
|
||||
|
||||
// Obtain information to fill out the profile.
|
||||
viewModel.obtainAccount(accountId)
|
||||
|
||||
val activeAccount = accountManager.activeAccount
|
||||
|
||||
if (accountId == activeAccount?.accountId) {
|
||||
isSelf = true
|
||||
updateButtons()
|
||||
} else {
|
||||
isSelf = false
|
||||
viewModel.obtainRelationship(accountId)
|
||||
}
|
||||
|
||||
// setup the RecyclerView for the account fields
|
||||
accountFieldList.isNestedScrollingEnabled = false
|
||||
accountFieldList.layoutManager = LinearLayoutManager(this)
|
||||
accountFieldList.adapter = accountFieldAdapter
|
||||
|
||||
// Setup the tabs and timeline pager.
|
||||
val adapter = AccountPagerAdapter(supportFragmentManager, accountId)
|
||||
val pageTitles = arrayOf(getString(R.string.title_statuses), getString(R.string.title_statuses_with_replies), getString(R.string.title_media))
|
||||
adapter.setPageTitles(pageTitles)
|
||||
accountFragmentViewPager.pageMargin = resources.getDimensionPixelSize(R.dimen.tab_page_margin)
|
||||
val pageMarginDrawable = ThemeUtils.getDrawable(this, R.attr.tab_page_margin_drawable,
|
||||
R.drawable.tab_page_margin_dark)
|
||||
accountFragmentViewPager.setPageMarginDrawable(pageMarginDrawable)
|
||||
accountFragmentViewPager.adapter = adapter
|
||||
accountFragmentViewPager.offscreenPageLimit = 2
|
||||
accountTabLayout.setupWithViewPager(accountFragmentViewPager)
|
||||
|
||||
val accountListClickListener = { v: View ->
|
||||
val type = when (v.id) {
|
||||
R.id.accountFollowers-> AccountListActivity.Type.FOLLOWERS
|
||||
R.id.accountFollowing -> AccountListActivity.Type.FOLLOWING
|
||||
else -> throw AssertionError()
|
||||
}
|
||||
val accountListIntent = AccountListActivity.newIntent(this, type, accountId)
|
||||
startActivity(accountListIntent)
|
||||
}
|
||||
accountFollowers.setOnClickListener(accountListClickListener)
|
||||
accountFollowing.setOnClickListener(accountListClickListener)
|
||||
|
||||
accountStatuses.setOnClickListener {
|
||||
// Make nice ripple effect on tab
|
||||
accountTabLayout.getTabAt(0)!!.select()
|
||||
val poorTabView = (accountTabLayout.getChildAt(0) as ViewGroup).getChildAt(0)
|
||||
poorTabView.isPressed = true
|
||||
accountTabLayout.postDelayed({ poorTabView.isPressed = false }, 300)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onAccountChanged(account: Account?) {
|
||||
if (account != null) {
|
||||
loadedAccount = account
|
||||
val usernameFormatted = getString(R.string.status_username_format, account.username)
|
||||
accountUsernameTextView.text = usernameFormatted
|
||||
accountDisplayNameTextView.text = CustomEmojiHelper.emojifyString(account.name, account.emojis, accountDisplayNameTextView)
|
||||
if (supportActionBar != null) {
|
||||
supportActionBar?.title = EmojiCompat.get().process(account.name)
|
||||
|
||||
val subtitle = String.format(getString(R.string.status_username_format),
|
||||
account.username)
|
||||
supportActionBar?.subtitle = subtitle
|
||||
}
|
||||
val emojifiedNote = CustomEmojiHelper.emojifyText(account.note, account.emojis, accountNoteTextView)
|
||||
LinkHelper.setClickableText(accountNoteTextView, emojifiedNote, null, this)
|
||||
|
||||
accountLockedImageView.visible(account.locked)
|
||||
accountBadgeTextView.visible(account.bot)
|
||||
|
||||
Picasso.with(this)
|
||||
.load(account.avatar)
|
||||
.transform(RoundedTransformation(25f))
|
||||
.placeholder(R.drawable.avatar_default)
|
||||
.into(accountAvatarImageView)
|
||||
Picasso.with(this)
|
||||
.load(account.header)
|
||||
.into(accountHeaderImageView)
|
||||
|
||||
accountFieldAdapter.fields = account.fields
|
||||
accountFieldAdapter.emojis = account.emojis
|
||||
accountFieldAdapter.notifyDataSetChanged()
|
||||
|
||||
if (account.moved != null) {
|
||||
val movedAccount = account.moved
|
||||
|
||||
accountMovedView.show()
|
||||
|
||||
// necessary because accountMovedView is now replaced in layout hierachy
|
||||
findViewById<View>(R.id.accountMovedView).setOnClickListener {
|
||||
onViewAccount(movedAccount.id)
|
||||
}
|
||||
|
||||
accountMovedDisplayName.text = movedAccount.name
|
||||
accountMovedUsername.text = getString(R.string.status_username_format, movedAccount.username)
|
||||
|
||||
Picasso.with(this)
|
||||
.load(movedAccount.avatar)
|
||||
.transform(RoundedTransformation(25f))
|
||||
.placeholder(R.drawable.avatar_default)
|
||||
.into(accountMovedAvatar)
|
||||
|
||||
accountMovedText.text = getString(R.string.account_moved_description, movedAccount.displayName)
|
||||
|
||||
// this is necessary because API 19 can't handle vector compound drawables
|
||||
val movedIcon = ContextCompat.getDrawable(this, R.drawable.ic_briefcase)?.mutate()
|
||||
val textColor = ThemeUtils.getColor(this, android.R.attr.textColorTertiary)
|
||||
movedIcon?.setColorFilter(textColor, PorterDuff.Mode.SRC_IN)
|
||||
|
||||
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(accountMovedText, movedIcon, null, null, null)
|
||||
|
||||
accountFollowers.hide()
|
||||
accountFollowing.hide()
|
||||
accountStatuses.hide()
|
||||
accountTabLayout.hide()
|
||||
accountFragmentViewPager.hide()
|
||||
accountTabBottomShadow.hide()
|
||||
}
|
||||
|
||||
val numberFormat = NumberFormat.getNumberInstance()
|
||||
accountFollowersTextView.text = numberFormat.format(account.followersCount)
|
||||
accountFollowingTextView.text = numberFormat.format(account.followingCount)
|
||||
accountStatusesTextView.text = numberFormat.format(account.statusesCount)
|
||||
|
||||
accountFloatingActionButton.setOnClickListener { _ -> mention() }
|
||||
|
||||
accountFollowButton.setOnClickListener { _ ->
|
||||
if (isSelf) {
|
||||
val intent = Intent(this@AccountActivity, EditProfileActivity::class.java)
|
||||
startActivityForResult(intent, EDIT_ACCOUNT)
|
||||
return@setOnClickListener
|
||||
}
|
||||
when (followState) {
|
||||
AccountActivity.FollowState.NOT_FOLLOWING -> {
|
||||
viewModel.changeFollowState(accountId)
|
||||
}
|
||||
AccountActivity.FollowState.REQUESTED -> {
|
||||
showFollowRequestPendingDialog()
|
||||
}
|
||||
AccountActivity.FollowState.FOLLOWING -> {
|
||||
showUnfollowWarningDialog()
|
||||
}
|
||||
}
|
||||
updateFollowButton()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
|
||||
//reload account when returning from EditProfileActivity
|
||||
if(requestCode == EDIT_ACCOUNT && resultCode == Activity.RESULT_OK) {
|
||||
viewModel.obtainAccount(accountId, true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.putString(KEY_ACCOUNT_ID, accountId)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
private fun onRelationshipChanged(relation: Relationship) {
|
||||
followState = when {
|
||||
relation.following -> FollowState.FOLLOWING
|
||||
relation.requested -> FollowState.REQUESTED
|
||||
else -> FollowState.NOT_FOLLOWING
|
||||
}
|
||||
blocking = relation.blocking
|
||||
muting = relation.muting
|
||||
showingReblogs = relation.showingReblogs
|
||||
|
||||
accountFollowsYouTextView.visible(relation.followedBy)
|
||||
|
||||
updateButtons()
|
||||
}
|
||||
|
||||
private fun reload() {
|
||||
viewModel.obtainAccount(accountId, true)
|
||||
viewModel.obtainRelationship(accountId)
|
||||
}
|
||||
|
||||
private fun updateFollowButton() {
|
||||
if(isSelf) {
|
||||
accountFollowButton.setText(R.string.action_edit_own_profile)
|
||||
return
|
||||
}
|
||||
when (followState) {
|
||||
AccountActivity.FollowState.NOT_FOLLOWING -> {
|
||||
accountFollowButton.setText(R.string.action_follow)
|
||||
}
|
||||
AccountActivity.FollowState.REQUESTED -> {
|
||||
accountFollowButton.setText(R.string.state_follow_requested)
|
||||
}
|
||||
AccountActivity.FollowState.FOLLOWING -> {
|
||||
accountFollowButton.setText(R.string.action_unfollow)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateButtons() {
|
||||
invalidateOptionsMenu()
|
||||
|
||||
if (!blocking && loadedAccount?.moved == null) {
|
||||
|
||||
accountFollowButton.show()
|
||||
updateFollowButton()
|
||||
|
||||
if(isSelf) {
|
||||
accountFloatingActionButton.hide()
|
||||
} else {
|
||||
accountFloatingActionButton.show()
|
||||
}
|
||||
|
||||
} else {
|
||||
accountFloatingActionButton.hide()
|
||||
accountFollowButton.hide()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.account_toolbar, menu)
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
||||
if (!isSelf) {
|
||||
val follow = menu.findItem(R.id.action_follow)
|
||||
follow.title = if (followState == FollowState.NOT_FOLLOWING) {
|
||||
getString(R.string.action_follow)
|
||||
} else {
|
||||
getString(R.string.action_unfollow)
|
||||
}
|
||||
|
||||
follow.isVisible = followState != FollowState.REQUESTED
|
||||
|
||||
val block = menu.findItem(R.id.action_block)
|
||||
block.title = if (blocking) {
|
||||
getString(R.string.action_unblock)
|
||||
} else {
|
||||
getString(R.string.action_block)
|
||||
}
|
||||
|
||||
val mute = menu.findItem(R.id.action_mute)
|
||||
mute.title = if (muting) {
|
||||
getString(R.string.action_unmute)
|
||||
} else {
|
||||
getString(R.string.action_mute)
|
||||
}
|
||||
|
||||
if (followState == FollowState.FOLLOWING) {
|
||||
val showReblogs = menu.findItem(R.id.action_show_reblogs)
|
||||
showReblogs.title = if (showingReblogs) {
|
||||
getString(R.string.action_hide_reblogs)
|
||||
} else {
|
||||
getString(R.string.action_show_reblogs)
|
||||
}
|
||||
|
||||
} else {
|
||||
menu.removeItem(R.id.action_show_reblogs)
|
||||
}
|
||||
|
||||
} else {
|
||||
// It shouldn't be possible to block or follow yourself.
|
||||
menu.removeItem(R.id.action_follow)
|
||||
menu.removeItem(R.id.action_block)
|
||||
menu.removeItem(R.id.action_mute)
|
||||
menu.removeItem(R.id.action_show_reblogs)
|
||||
}
|
||||
return super.onPrepareOptionsMenu(menu)
|
||||
}
|
||||
|
||||
private fun showFollowRequestPendingDialog() {
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage(R.string.dialog_message_cancel_follow_request)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> viewModel.changeFollowState(accountId) }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun showUnfollowWarningDialog() {
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage(R.string.dialog_unfollow_warning)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> viewModel.changeFollowState(accountId) }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun mention() {
|
||||
loadedAccount?.let {
|
||||
val intent = ComposeActivity.IntentBuilder()
|
||||
.mentionedUsernames(setOf(it.username))
|
||||
.build(this)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewTag(tag: String) {
|
||||
val intent = Intent(this, ViewTagActivity::class.java)
|
||||
intent.putExtra("hashtag", tag)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
override fun onViewAccount(id: String) {
|
||||
val intent = Intent(this, AccountActivity::class.java)
|
||||
intent.putExtra("id", id)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
override fun onViewUrl(url: String) {
|
||||
viewUrl(url)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
onBackPressed()
|
||||
return true
|
||||
}
|
||||
R.id.action_mention -> {
|
||||
mention()
|
||||
return true
|
||||
}
|
||||
R.id.action_open_in_web -> {
|
||||
// If the account isn't loaded yet, eat the input.
|
||||
if (loadedAccount != null) {
|
||||
LinkHelper.openLink(loadedAccount?.url, this)
|
||||
}
|
||||
return true
|
||||
}
|
||||
R.id.action_follow -> {
|
||||
viewModel.changeFollowState(accountId)
|
||||
return true
|
||||
}
|
||||
R.id.action_block -> {
|
||||
viewModel.changeBlockState(accountId)
|
||||
return true
|
||||
}
|
||||
R.id.action_mute -> {
|
||||
viewModel.changeMuteState(accountId)
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.action_show_reblogs -> {
|
||||
viewModel.changeShowReblogsState(accountId)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun getActionButton(): FloatingActionButton? {
|
||||
return if (!isSelf && !blocking) {
|
||||
accountFloatingActionButton
|
||||
} else null
|
||||
}
|
||||
|
||||
override fun supportFragmentInjector(): AndroidInjector<Fragment> {
|
||||
return dispatchingAndroidInjector
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val EDIT_ACCOUNT = 1457
|
||||
|
||||
private const val KEY_ACCOUNT_ID = "id"
|
||||
private val argbEvaluator = ArgbEvaluator()
|
||||
|
||||
@JvmStatic
|
||||
fun getIntent(context: Context, accountId: String): Intent {
|
||||
val intent = Intent(context, AccountActivity::class.java)
|
||||
intent.putExtra(KEY_ACCOUNT_ID, accountId)
|
||||
return intent
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -117,8 +117,7 @@ abstract class BottomSheetActivity : BaseActivity() {
|
|||
}
|
||||
|
||||
open fun viewAccount(id: String) {
|
||||
val intent = Intent(this, AccountActivity::class.java)
|
||||
intent.putExtra("id", id)
|
||||
val intent = AccountActivity.getIntent(this, id)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
|
|
|
@ -155,7 +155,6 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
if (!headerChanged) {
|
||||
Picasso.with(headerPreview.context)
|
||||
.load(me.header)
|
||||
.placeholder(R.drawable.account_header_default)
|
||||
.into(headerPreview)
|
||||
}
|
||||
}
|
||||
|
@ -289,6 +288,7 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
|
||||
if (displayName == null && note == null && locked == null && avatar == null && header == null) {
|
||||
/** if nothing has changed, there is no need to make a network request */
|
||||
setResult(Activity.RESULT_OK)
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
@ -302,6 +302,7 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
privatePreferences.edit()
|
||||
.putBoolean("refreshProfileHeader", true)
|
||||
.apply()
|
||||
setResult(Activity.RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
|
||||
|
|
|
@ -393,8 +393,7 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
|
|||
|
||||
//open profile when active image was clicked
|
||||
if (current && activeAccount != null) {
|
||||
Intent intent = new Intent(MainActivity.this, AccountActivity.class);
|
||||
intent.putExtra("id", activeAccount.getAccountId());
|
||||
Intent intent = AccountActivity.getIntent(this, activeAccount.getAccountId());
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
@ -478,7 +477,6 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
|
|||
|
||||
Picasso.with(MainActivity.this)
|
||||
.load(me.getHeader())
|
||||
.placeholder(R.drawable.account_header_default)
|
||||
.into(background);
|
||||
|
||||
accountManager.updateActiveAccount(me);
|
||||
|
|
|
@ -189,7 +189,7 @@ public class ReportActivity extends BaseActivity implements Injectable {
|
|||
onFetchStatusesFailure((Exception) t);
|
||||
}
|
||||
};
|
||||
mastodonApi.accountStatuses(accountId, null, null, null, null)
|
||||
mastodonApi.accountStatuses(accountId, null, null, null, null, null)
|
||||
.enqueue(callback);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/* Copyright 2018 Conny Duck
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.adapter
|
||||
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.entity.Emoji
|
||||
import com.keylesspalace.tusky.entity.Field
|
||||
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||
import com.keylesspalace.tusky.util.CustomEmojiHelper
|
||||
import com.keylesspalace.tusky.util.LinkHelper
|
||||
import kotlinx.android.synthetic.main.item_account_field.view.*
|
||||
|
||||
class AccountFieldAdapter(private val linkListener: LinkListener) : RecyclerView.Adapter<AccountFieldAdapter.ViewHolder>() {
|
||||
|
||||
var emojis: List<Emoji> = emptyList()
|
||||
var fields: List<Field> = emptyList()
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return fields.size
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccountFieldAdapter.ViewHolder {
|
||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_account_field, parent, false)
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(viewHolder: AccountFieldAdapter.ViewHolder, position: Int) {
|
||||
viewHolder.nameTextView.text = fields[position].name
|
||||
val emojifiedValue = CustomEmojiHelper.emojifyText(fields[position].value, emojis, viewHolder.valueTextView)
|
||||
LinkHelper.setClickableText(viewHolder.valueTextView, emojifiedValue, null, linkListener)
|
||||
}
|
||||
|
||||
class ViewHolder(rootView: View) : RecyclerView.ViewHolder(rootView) {
|
||||
val nameTextView: TextView = rootView.accountFieldName
|
||||
val valueTextView: TextView = rootView.accountFieldValue
|
||||
}
|
||||
|
||||
}
|
|
@ -33,7 +33,8 @@ import javax.inject.Singleton
|
|||
AndroidInjectionModule::class,
|
||||
ActivitiesModule::class,
|
||||
ServicesModule::class,
|
||||
BroadcastReceiverModule::class
|
||||
BroadcastReceiverModule::class,
|
||||
ViewModelModule::class
|
||||
])
|
||||
interface AppComponent {
|
||||
@Component.Builder
|
||||
|
|
|
@ -75,7 +75,7 @@ class NetworkModule {
|
|||
.apply {
|
||||
addInterceptor(InstanceSwitchAuthInterceptor(accountManager))
|
||||
if (BuildConfig.DEBUG) {
|
||||
addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC))
|
||||
addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
|
||||
}
|
||||
}
|
||||
.build()
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
// from https://proandroiddev.com/viewmodel-with-dagger2-architecture-components-2e06f06c9455
|
||||
|
||||
package com.keylesspalace.tusky.di
|
||||
|
||||
import android.arch.lifecycle.ViewModel
|
||||
import android.arch.lifecycle.ViewModelProvider
|
||||
import com.keylesspalace.tusky.viewmodel.AccountViewModel
|
||||
import dagger.Binds
|
||||
import dagger.MapKey
|
||||
import dagger.Module
|
||||
import dagger.multibindings.IntoMap
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
import javax.inject.Singleton
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@Singleton
|
||||
class ViewModelFactory @Inject constructor(private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>) : ViewModelProvider.Factory {
|
||||
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T = viewModels[modelClass]?.get() as T
|
||||
}
|
||||
|
||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
|
||||
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
|
||||
@MapKey
|
||||
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
|
||||
|
||||
@Module
|
||||
abstract class ViewModelModule {
|
||||
|
||||
@Binds
|
||||
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(AccountViewModel::class)
|
||||
internal abstract fun accountViewModel(viewModel: AccountViewModel): ViewModel
|
||||
|
||||
//Add more ViewModels here
|
||||
}
|
|
@ -15,7 +15,6 @@
|
|||
|
||||
package com.keylesspalace.tusky.entity
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.text.Spanned
|
||||
|
@ -26,7 +25,6 @@ import kotlinx.android.parcel.Parceler
|
|||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.android.parcel.WriteWith
|
||||
|
||||
@SuppressLint("ParcelCreator")
|
||||
@Parcelize
|
||||
data class Account(
|
||||
val id: String,
|
||||
|
@ -41,7 +39,11 @@ data class Account(
|
|||
@SerializedName("followers_count") val followersCount: Int,
|
||||
@SerializedName("following_count") val followingCount: Int,
|
||||
@SerializedName("statuses_count") val statusesCount: Int,
|
||||
val source: AccountSource?
|
||||
val source: AccountSource?,
|
||||
val bot: Boolean,
|
||||
val emojis: List<Emoji> = emptyList(),
|
||||
val fields: List<Field> = emptyList(),
|
||||
val moved: Account? = null
|
||||
|
||||
) : Parcelable {
|
||||
|
||||
|
@ -62,20 +64,25 @@ data class Account(
|
|||
return account?.id == this.id
|
||||
}
|
||||
|
||||
object SpannedParceler : Parceler<Spanned> {
|
||||
override fun create(parcel: Parcel) = HtmlUtils.fromHtml(parcel.readString())
|
||||
|
||||
override fun Spanned.write(parcel: Parcel, flags: Int) {
|
||||
parcel.writeString(HtmlUtils.toHtml(this))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
@SuppressLint("ParcelCreator")
|
||||
data class AccountSource(
|
||||
val privacy: Status.Visibility,
|
||||
val sensitive: Boolean,
|
||||
val note: String
|
||||
): Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class Field (
|
||||
val name:String,
|
||||
val value: @WriteWith<SpannedParceler>() Spanned
|
||||
): Parcelable
|
||||
|
||||
object SpannedParceler : Parceler<Spanned> {
|
||||
override fun create(parcel: Parcel): Spanned = HtmlUtils.fromHtml(parcel.readString())
|
||||
|
||||
override fun Spanned.write(parcel: Parcel, flags: Int) {
|
||||
parcel.writeString(HtmlUtils.toHtml(this))
|
||||
}
|
||||
}
|
|
@ -23,5 +23,6 @@ data class Relationship (
|
|||
@SerializedName("followed_by") val followedBy: Boolean,
|
||||
val blocking: Boolean,
|
||||
val muting: Boolean,
|
||||
val requested: Boolean
|
||||
val requested: Boolean,
|
||||
@SerializedName("showing_reblogs") val showingReblogs: Boolean
|
||||
)
|
||||
|
|
|
@ -167,9 +167,11 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi
|
|||
|
||||
@Override
|
||||
public void onViewAccount(String id) {
|
||||
Intent intent = new Intent(getContext(), AccountActivity.class);
|
||||
intent.putExtra("id", id);
|
||||
startActivity(intent);
|
||||
Context context = getContext();
|
||||
if(context != null) {
|
||||
Intent intent = AccountActivity.getIntent(context, id);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -156,10 +156,10 @@ class AccountMediaFragment : BaseFragment(), Injectable {
|
|||
if (fetchingStatus != FetchingStatus.NOT_FETCHING) return@setOnRefreshListener
|
||||
currentCall = if (statuses.isEmpty()) {
|
||||
fetchingStatus = FetchingStatus.INITIAL_FETCHING
|
||||
api.accountStatuses(accountId, null, null, null, true)
|
||||
api.accountStatuses(accountId, null, null, null, null, true)
|
||||
} else {
|
||||
fetchingStatus = FetchingStatus.REFRESHING
|
||||
api.accountStatuses(accountId, null, statuses[0].id, null, true)
|
||||
api.accountStatuses(accountId, null, statuses[0].id, null, null, true)
|
||||
}
|
||||
currentCall?.enqueue(callback)
|
||||
|
||||
|
@ -179,7 +179,7 @@ class AccountMediaFragment : BaseFragment(), Injectable {
|
|||
statuses.lastOrNull()?.let { last ->
|
||||
Log.d(TAG, "Requesting statuses with max_id: ${last.id}, (bottom)")
|
||||
fetchingStatus = FetchingStatus.FETCHING_BOTTOM
|
||||
currentCall = api.accountStatuses(accountId, last.id, null, null, true)
|
||||
currentCall = api.accountStatuses(accountId, last.id, null, null, null, true)
|
||||
currentCall?.enqueue(bottomCallback)
|
||||
}
|
||||
}
|
||||
|
@ -195,7 +195,7 @@ class AccountMediaFragment : BaseFragment(), Injectable {
|
|||
val accountId = arguments?.getString(ACCOUNT_ID_ARG)
|
||||
if (fetchingStatus == FetchingStatus.NOT_FETCHING && statuses.isEmpty()) {
|
||||
fetchingStatus = FetchingStatus.INITIAL_FETCHING
|
||||
currentCall = api.accountStatuses(accountId, null, null, null, true)
|
||||
currentCall = api.accountStatuses(accountId, null, null, null, null, true)
|
||||
currentCall?.enqueue(callback)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -217,8 +217,7 @@ class SearchFragment : SFragment(), StatusActionListener, Injectable {
|
|||
}
|
||||
|
||||
override fun onViewAccount(id: String) {
|
||||
val intent = Intent(context, AccountActivity::class.java)
|
||||
intent.putExtra("id", id)
|
||||
val intent = AccountActivity.getIntent(requireContext(), id)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
|
|
|
@ -103,6 +103,7 @@ public class TimelineFragment extends SFragment implements
|
|||
PUBLIC_FEDERATED,
|
||||
TAG,
|
||||
USER,
|
||||
USER_WITH_REPLIES,
|
||||
FAVOURITES,
|
||||
LIST
|
||||
}
|
||||
|
@ -200,7 +201,7 @@ public class TimelineFragment extends SFragment implements
|
|||
Bundle savedInstanceState) {
|
||||
Bundle arguments = Objects.requireNonNull(getArguments());
|
||||
kind = Kind.valueOf(arguments.getString(KIND_ARG));
|
||||
if (kind == Kind.TAG || kind == Kind.USER || kind == Kind.LIST) {
|
||||
if (kind == Kind.TAG || kind == Kind.USER || kind == Kind.USER_WITH_REPLIES|| kind == Kind.LIST) {
|
||||
hashtagOrId = arguments.getString(HASHTAG_OR_ID_ARG);
|
||||
}
|
||||
|
||||
|
@ -309,14 +310,20 @@ public class TimelineFragment extends SFragment implements
|
|||
removeAllByAccountId(id);
|
||||
}
|
||||
} else if (event instanceof BlockEvent) {
|
||||
String id = ((BlockEvent) event).getAccountId();
|
||||
removeAllByAccountId(id);
|
||||
if (kind != Kind.USER && kind != Kind.USER_WITH_REPLIES) {
|
||||
String id = ((BlockEvent) event).getAccountId();
|
||||
removeAllByAccountId(id);
|
||||
}
|
||||
} else if (event instanceof MuteEvent) {
|
||||
String id = ((MuteEvent) event).getAccountId();
|
||||
removeAllByAccountId(id);
|
||||
if (kind != Kind.USER && kind != Kind.USER_WITH_REPLIES) {
|
||||
String id = ((MuteEvent) event).getAccountId();
|
||||
removeAllByAccountId(id);
|
||||
}
|
||||
} else if (event instanceof StatusDeletedEvent) {
|
||||
String id = ((StatusDeletedEvent) event).getStatusId();
|
||||
deleteStatusById(id);
|
||||
if (kind != Kind.USER && kind != Kind.USER_WITH_REPLIES) {
|
||||
String id = ((StatusDeletedEvent) event).getStatusId();
|
||||
deleteStatusById(id);
|
||||
}
|
||||
} else if (event instanceof StatusComposedEvent) {
|
||||
Status status = ((StatusComposedEvent) event).getStatus();
|
||||
handleStatusComposeEvent(status);
|
||||
|
@ -587,7 +594,7 @@ public class TimelineFragment extends SFragment implements
|
|||
|
||||
@Override
|
||||
public void onViewAccount(String id) {
|
||||
if (kind == Kind.USER && hashtagOrId.equals(id)) {
|
||||
if ((kind == Kind.USER || kind == Kind.USER_WITH_REPLIES) && hashtagOrId.equals(id)) {
|
||||
/* If already viewing an account page, then any requests to view that account page
|
||||
* should be ignored. */
|
||||
return;
|
||||
|
@ -724,7 +731,9 @@ public class TimelineFragment extends SFragment implements
|
|||
case TAG:
|
||||
return api.hashtagTimeline(tagOrId, null, fromId, uptoId, LOAD_AT_ONCE);
|
||||
case USER:
|
||||
return api.accountStatuses(tagOrId, fromId, uptoId, LOAD_AT_ONCE, null);
|
||||
return api.accountStatuses(tagOrId, fromId, uptoId, LOAD_AT_ONCE, true, null);
|
||||
case USER_WITH_REPLIES:
|
||||
return api.accountStatuses(tagOrId, fromId, uptoId, LOAD_AT_ONCE, null, null);
|
||||
case FAVOURITES:
|
||||
return api.favourites(fromId, uptoId, LOAD_AT_ONCE);
|
||||
case LIST:
|
||||
|
@ -1024,6 +1033,7 @@ public class TimelineFragment extends SFragment implements
|
|||
case PUBLIC_LOCAL:
|
||||
break;
|
||||
case USER:
|
||||
case USER_WITH_REPLIES:
|
||||
if (status.getAccount().getId().equals(hashtagOrId)) {
|
||||
break;
|
||||
} else {
|
||||
|
|
|
@ -37,6 +37,7 @@ import okhttp3.MultipartBody;
|
|||
import okhttp3.RequestBody;
|
||||
import okhttp3.ResponseBody;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.DELETE;
|
||||
import retrofit2.http.Field;
|
||||
import retrofit2.http.FormUrlEncoded;
|
||||
|
@ -60,12 +61,14 @@ public interface MastodonApi {
|
|||
@Query("max_id") String maxId,
|
||||
@Query("since_id") String sinceId,
|
||||
@Query("limit") Integer limit);
|
||||
|
||||
@GET("api/v1/timelines/public")
|
||||
Call<List<Status>> publicTimeline(
|
||||
@Query("local") Boolean local,
|
||||
@Query("max_id") String maxId,
|
||||
@Query("since_id") String sinceId,
|
||||
@Query("limit") Integer limit);
|
||||
|
||||
@GET("api/v1/timelines/tag/{hashtag}")
|
||||
Call<List<Status>> hashtagTimeline(
|
||||
@Path("hashtag") String hashtag,
|
||||
|
@ -73,6 +76,7 @@ public interface MastodonApi {
|
|||
@Query("max_id") String maxId,
|
||||
@Query("since_id") String sinceId,
|
||||
@Query("limit") Integer limit);
|
||||
|
||||
@GET("api/v1/timelines/list/{listId}")
|
||||
Call<List<Status>> listTimeline(
|
||||
@Path("listId") String listId,
|
||||
|
@ -85,17 +89,21 @@ public interface MastodonApi {
|
|||
@Query("max_id") String maxId,
|
||||
@Query("since_id") String sinceId,
|
||||
@Query("limit") Integer limit);
|
||||
|
||||
@GET("api/v1/notifications")
|
||||
Call<List<Notification>> notificationsWithAuth(
|
||||
@Header("Authorization") String auth, @Header(DOMAIN_HEADER) String domain);
|
||||
|
||||
@POST("api/v1/notifications/clear")
|
||||
Call<ResponseBody> clearNotifications();
|
||||
|
||||
@GET("api/v1/notifications/{id}")
|
||||
Call<Notification> notification(@Path("id") String notificationId);
|
||||
|
||||
@Multipart
|
||||
@POST("api/v1/media")
|
||||
Call<Attachment> uploadMedia(@Part MultipartBody.Part file);
|
||||
|
||||
@FormUrlEncoded
|
||||
@PUT("api/v1/media/{mediaId}")
|
||||
Call<Attachment> updateMedia(@Path("mediaId") String mediaId,
|
||||
|
@ -113,30 +121,39 @@ public interface MastodonApi {
|
|||
@Field("sensitive") Boolean sensitive,
|
||||
@Field("media_ids[]") List<String> mediaIds,
|
||||
@Header("Idempotency-Key") String idempotencyKey);
|
||||
|
||||
@GET("api/v1/statuses/{id}")
|
||||
Call<Status> status(@Path("id") String statusId);
|
||||
|
||||
@GET("api/v1/statuses/{id}/context")
|
||||
Call<StatusContext> statusContext(@Path("id") String statusId);
|
||||
|
||||
@GET("api/v1/statuses/{id}/reblogged_by")
|
||||
Call<List<Account>> statusRebloggedBy(
|
||||
@Path("id") String statusId,
|
||||
@Query("max_id") String maxId,
|
||||
@Query("since_id") String sinceId,
|
||||
@Query("limit") Integer limit);
|
||||
|
||||
@GET("api/v1/statuses/{id}/favourited_by")
|
||||
Call<List<Account>> statusFavouritedBy(
|
||||
@Path("id") String statusId,
|
||||
@Query("max_id") String maxId,
|
||||
@Query("since_id") String sinceId,
|
||||
@Query("limit") Integer limit);
|
||||
|
||||
@DELETE("api/v1/statuses/{id}")
|
||||
Call<ResponseBody> deleteStatus(@Path("id") String statusId);
|
||||
|
||||
@POST("api/v1/statuses/{id}/reblog")
|
||||
Call<Status> reblogStatus(@Path("id") String statusId);
|
||||
|
||||
@POST("api/v1/statuses/{id}/unreblog")
|
||||
Call<Status> unreblogStatus(@Path("id") String statusId);
|
||||
|
||||
@POST("api/v1/statuses/{id}/favourite")
|
||||
Call<Status> favouriteStatus(@Path("id") String statusId);
|
||||
|
||||
@POST("api/v1/statuses/{id}/unfavourite")
|
||||
Call<Status> unfavouriteStatus(@Path("id") String statusId);
|
||||
|
||||
|
@ -166,9 +183,8 @@ public interface MastodonApi {
|
|||
* @param maxId Only statuses with ID less than maxID will be returned
|
||||
* @param sinceId Only statuses with ID bigger than sinceID will be returned
|
||||
* @param limit Limit returned statuses (current API limits: default - 20, max - 40)
|
||||
* @param onlyMedia Should server return only statuses which contain media. Caution! The server
|
||||
* works in a weird way so if any value if present at this field it will be
|
||||
* interpreted as "true". Pass null to return all statuses.
|
||||
* @param excludeReplies only return statuses that are no replies
|
||||
* @param onlyMedia only return statuses that have media attached
|
||||
* @return
|
||||
*/
|
||||
@GET("api/v1/accounts/{id}/statuses")
|
||||
|
@ -177,29 +193,39 @@ public interface MastodonApi {
|
|||
@Query("max_id") String maxId,
|
||||
@Query("since_id") String sinceId,
|
||||
@Query("limit") Integer limit,
|
||||
@Nullable @Query("exclude_replies") Boolean excludeReplies,
|
||||
@Nullable @Query("only_media") Boolean onlyMedia);
|
||||
|
||||
@GET("api/v1/accounts/{id}/followers")
|
||||
Call<List<Account>> accountFollowers(
|
||||
@Path("id") String accountId,
|
||||
@Query("max_id") String maxId,
|
||||
@Query("since_id") String sinceId,
|
||||
@Query("limit") Integer limit);
|
||||
|
||||
@GET("api/v1/accounts/{id}/following")
|
||||
Call<List<Account>> accountFollowing(
|
||||
@Path("id") String accountId,
|
||||
@Query("max_id") String maxId,
|
||||
@Query("since_id") String sinceId,
|
||||
@Query("limit") Integer limit);
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("api/v1/accounts/{id}/follow")
|
||||
Call<Relationship> followAccount(@Path("id") String accountId);
|
||||
Call<Relationship> followAccount(@Path("id") String accountId, @Field("reblogs") boolean showReblogs);
|
||||
|
||||
@POST("api/v1/accounts/{id}/unfollow")
|
||||
Call<Relationship> unfollowAccount(@Path("id") String accountId);
|
||||
|
||||
@POST("api/v1/accounts/{id}/block")
|
||||
Call<Relationship> blockAccount(@Path("id") String accountId);
|
||||
|
||||
@POST("api/v1/accounts/{id}/unblock")
|
||||
Call<Relationship> unblockAccount(@Path("id") String accountId);
|
||||
|
||||
@POST("api/v1/accounts/{id}/mute")
|
||||
Call<Relationship> muteAccount(@Path("id") String accountId);
|
||||
|
||||
@POST("api/v1/accounts/{id}/unmute")
|
||||
Call<Relationship> unmuteAccount(@Path("id") String accountId);
|
||||
|
||||
|
@ -229,8 +255,10 @@ public interface MastodonApi {
|
|||
@Query("max_id") String maxId,
|
||||
@Query("since_id") String sinceId,
|
||||
@Query("limit") Integer limit);
|
||||
|
||||
@POST("api/v1/follow_requests/{id}/authorize")
|
||||
Call<Relationship> authorizeFollowRequest(@Path("id") String accountId);
|
||||
|
||||
@POST("api/v1/follow_requests/{id}/reject")
|
||||
Call<Relationship> rejectFollowRequest(@Path("id") String accountId);
|
||||
|
||||
|
|
|
@ -42,6 +42,9 @@ public class AccountPagerAdapter extends FragmentPagerAdapter {
|
|||
return TimelineFragment.newInstance(TimelineFragment.Kind.USER, accountId);
|
||||
}
|
||||
case 1: {
|
||||
return TimelineFragment.newInstance(TimelineFragment.Kind.USER_WITH_REPLIES, accountId);
|
||||
}
|
||||
case 2: {
|
||||
return AccountMediaFragment.newInstance(accountId);
|
||||
}
|
||||
default: {
|
||||
|
@ -52,7 +55,7 @@ public class AccountPagerAdapter extends FragmentPagerAdapter {
|
|||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return 2;
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package com.keylesspalace.tusky.util
|
||||
|
||||
sealed class Resource<T>(open val data: T?)
|
||||
|
||||
class Loading<T> (override val data: T? = null) : Resource<T>(data)
|
||||
|
||||
class Success<T> (override val data: T? = null) : Resource<T>(data)
|
||||
|
||||
class Error<T> (override val data: T? = null, val errorMessage: String? = null): Resource<T>(data)
|
|
@ -0,0 +1,19 @@
|
|||
package com.keylesspalace.tusky.util
|
||||
|
||||
import android.view.View
|
||||
|
||||
fun View.show() {
|
||||
this.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
fun View.hide() {
|
||||
this.visibility = View.GONE
|
||||
}
|
||||
|
||||
fun View.visible(visible: Boolean) {
|
||||
this.visibility = if(visible) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
package com.keylesspalace.tusky.viewmodel
|
||||
|
||||
import android.arch.lifecycle.MutableLiveData
|
||||
import android.arch.lifecycle.ViewModel
|
||||
import com.keylesspalace.tusky.appstore.BlockEvent
|
||||
import com.keylesspalace.tusky.appstore.EventHub
|
||||
import com.keylesspalace.tusky.appstore.MuteEvent
|
||||
import com.keylesspalace.tusky.appstore.UnfollowEvent
|
||||
import com.keylesspalace.tusky.entity.Account
|
||||
import com.keylesspalace.tusky.entity.Relationship
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.util.Error
|
||||
import com.keylesspalace.tusky.util.Loading
|
||||
import com.keylesspalace.tusky.util.Resource
|
||||
import com.keylesspalace.tusky.util.Success
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import javax.inject.Inject
|
||||
|
||||
class AccountViewModel @Inject constructor(
|
||||
private val mastodonApi: MastodonApi,
|
||||
private val eventHub: EventHub
|
||||
): ViewModel() {
|
||||
|
||||
val accountData = MutableLiveData<Resource<Account>>()
|
||||
val relationshipData = MutableLiveData<Resource<Relationship>>()
|
||||
|
||||
private val callList: MutableList<Call<*>> = mutableListOf()
|
||||
|
||||
|
||||
fun obtainAccount(accountId: String, reload: Boolean = false) {
|
||||
if(accountData.value == null || reload) {
|
||||
|
||||
accountData.postValue(Loading())
|
||||
|
||||
val call = mastodonApi.account(accountId)
|
||||
call.enqueue(object : Callback<Account> {
|
||||
override fun onResponse(call: Call<Account>,
|
||||
response: Response<Account>) {
|
||||
if (response.isSuccessful) {
|
||||
accountData.postValue(Success(response.body()))
|
||||
} else {
|
||||
accountData.postValue(Error())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<Account>, t: Throwable) {
|
||||
accountData.postValue(Error())
|
||||
}
|
||||
})
|
||||
|
||||
callList.add(call)
|
||||
}
|
||||
}
|
||||
|
||||
fun obtainRelationship(accountId: String, reload: Boolean = false) {
|
||||
if(relationshipData.value == null || reload) {
|
||||
|
||||
relationshipData.postValue(Loading())
|
||||
|
||||
val ids = listOf(accountId)
|
||||
val call = mastodonApi.relationships(ids)
|
||||
call.enqueue(object : Callback<List<Relationship>> {
|
||||
override fun onResponse(call: Call<List<Relationship>>,
|
||||
response: Response<List<Relationship>>) {
|
||||
val relationships = response.body()
|
||||
if (response.isSuccessful && relationships != null) {
|
||||
val relationship = relationships[0]
|
||||
relationshipData.postValue(Success(relationship))
|
||||
} else {
|
||||
relationshipData.postValue(Error())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<List<Relationship>>, t: Throwable) {
|
||||
relationshipData.postValue(Error())
|
||||
}
|
||||
})
|
||||
|
||||
callList.add(call)
|
||||
}
|
||||
}
|
||||
|
||||
fun changeFollowState(id: String) {
|
||||
if (relationshipData.value?.data?.following == true) {
|
||||
changeRelationship(RelationShipAction.UNFOLLOW, id)
|
||||
} else {
|
||||
changeRelationship(RelationShipAction.FOLLOW, id)
|
||||
}
|
||||
}
|
||||
|
||||
fun changeBlockState(id: String) {
|
||||
if (relationshipData.value?.data?.blocking == true) {
|
||||
changeRelationship(RelationShipAction.UNBLOCK, id)
|
||||
} else {
|
||||
changeRelationship(RelationShipAction.BLOCK, id)
|
||||
}
|
||||
}
|
||||
|
||||
fun changeMuteState(id: String) {
|
||||
if (relationshipData.value?.data?.muting == true) {
|
||||
changeRelationship(RelationShipAction.UNMUTE, id)
|
||||
} else {
|
||||
changeRelationship(RelationShipAction.MUTE, id)
|
||||
}
|
||||
}
|
||||
|
||||
fun changeShowReblogsState(id: String) {
|
||||
if (relationshipData.value?.data?.showingReblogs == true) {
|
||||
changeRelationship(RelationShipAction.FOLLOW, id, false)
|
||||
} else {
|
||||
changeRelationship(RelationShipAction.FOLLOW, id, true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeRelationship(relationshipAction: RelationShipAction, id: String, showReblogs: Boolean = true) {
|
||||
val relation = relationshipData.value?.data
|
||||
val account = accountData.value?.data
|
||||
|
||||
if(relation != null && account != null) {
|
||||
// optimistically post new state for faster response
|
||||
|
||||
val newRelation = when(relationshipAction) {
|
||||
RelationShipAction.FOLLOW -> {
|
||||
if (account.locked) {
|
||||
relation.copy(requested = true)
|
||||
} else {
|
||||
relation.copy(following = true)
|
||||
}
|
||||
}
|
||||
RelationShipAction.UNFOLLOW -> relation.copy(following = false)
|
||||
RelationShipAction.BLOCK -> relation.copy(blocking = true)
|
||||
RelationShipAction.UNBLOCK -> relation.copy(blocking = false)
|
||||
RelationShipAction.MUTE -> relation.copy(muting = true)
|
||||
RelationShipAction.UNMUTE -> relation.copy(muting = false)
|
||||
}
|
||||
relationshipData.postValue(Loading(newRelation))
|
||||
}
|
||||
|
||||
val callback = object : Callback<Relationship> {
|
||||
override fun onResponse(call: Call<Relationship>,
|
||||
response: Response<Relationship>) {
|
||||
val relationship = response.body()
|
||||
if (response.isSuccessful && relationship != null) {
|
||||
relationshipData.postValue(Success(relationship))
|
||||
|
||||
when (relationshipAction) {
|
||||
RelationShipAction.UNFOLLOW -> eventHub.dispatch(UnfollowEvent(id))
|
||||
RelationShipAction.BLOCK -> eventHub.dispatch(BlockEvent(id))
|
||||
RelationShipAction.MUTE -> eventHub.dispatch(MuteEvent(id))
|
||||
else -> {}
|
||||
}
|
||||
|
||||
} else {
|
||||
relationshipData.postValue(Error(relation))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<Relationship>, t: Throwable) {
|
||||
relationshipData.postValue(Error(relation))
|
||||
}
|
||||
}
|
||||
|
||||
val call = when(relationshipAction) {
|
||||
RelationShipAction.FOLLOW -> mastodonApi.followAccount(id, showReblogs)
|
||||
RelationShipAction.UNFOLLOW -> mastodonApi.unfollowAccount(id)
|
||||
RelationShipAction.BLOCK -> mastodonApi.blockAccount(id)
|
||||
RelationShipAction.UNBLOCK -> mastodonApi.unblockAccount(id)
|
||||
RelationShipAction.MUTE -> mastodonApi.muteAccount(id)
|
||||
RelationShipAction.UNMUTE -> mastodonApi.unmuteAccount(id)
|
||||
}
|
||||
|
||||
call.enqueue(callback)
|
||||
callList.add(call)
|
||||
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
callList.forEach {
|
||||
it.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
enum class RelationShipAction {
|
||||
FOLLOW, UNFOLLOW, BLOCK, UNBLOCK, MUTE, UNMUTE
|
||||
}
|
||||
|
||||
}
|
6
app/src/main/res/drawable-night/avatar_background.xml
Normal file
6
app/src/main/res/drawable-night/avatar_background.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/color_primary_dark_dark" />
|
||||
<corners android:radius="14dp"/>
|
||||
<size android:height="100dp" android:width="100dp"/>
|
||||
</shape>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<stroke android:width="1dp" android:color="@color/text_color_tertiary_dark"/>
|
||||
<corners android:radius="12dp"/>
|
||||
<padding android:bottom="2dp" android:top="2dp" android:left="8dp" android:right="8dp"/>
|
||||
</shape>
|
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<size android:width="700px" android:height="335px" />
|
||||
<solid android:color="@color/color_background_dark" />
|
||||
</shape>
|
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape>
|
||||
<gradient
|
||||
android:angle="90"
|
||||
android:startColor="#FF1a1c23"
|
||||
android:endColor="#001a1c23"
|
||||
android:type="linear" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
6
app/src/main/res/drawable/avatar_background.xml
Normal file
6
app/src/main/res/drawable/avatar_background.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/color_background_light" />
|
||||
<corners android:radius="14dp"/>
|
||||
<size android:height="100dp" android:width="100dp"/>
|
||||
</shape>
|
9
app/src/main/res/drawable/ic_briefcase.xml
Normal file
9
app/src/main/res/drawable/ic_briefcase.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="18dp"
|
||||
android:height="18dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M20,6C20.58,6 21.05,6.2 21.42,6.59C21.8,7 22,7.45 22,8V19C22,19.55 21.8,20 21.42,20.41C21.05,20.8 20.58,21 20,21H4C3.42,21 2.95,20.8 2.58,20.41C2.2,20 2,19.55 2,19V8C2,7.45 2.2,7 2.58,6.59C2.95,6.2 3.42,6 4,6H8V4C8,3.42 8.2,2.95 8.58,2.58C8.95,2.2 9.42,2 10,2H14C14.58,2 15.05,2.2 15.42,2.58C15.8,2.95 16,3.42 16,4V6H20M4,8V19H20V8H4M14,6V4H10V6H14Z" />
|
||||
</vector>
|
6
app/src/main/res/drawable/profile_badge_background.xml
Normal file
6
app/src/main/res/drawable/profile_badge_background.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<stroke android:width="1dp" android:color="@color/text_color_tertiary_light"/>
|
||||
<corners android:radius="12dp"/>
|
||||
<padding android:bottom="2dp" android:top="2dp" android:left="8dp" android:right="8dp"/>
|
||||
</shape>
|
|
@ -2,229 +2,295 @@
|
|||
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/activity_account"
|
||||
android:id="@+id/accountCoordinatorLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
android:orientation="vertical">
|
||||
android:fillViewport="true">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:id="@+id/account_app_bar_layout"
|
||||
android:id="@+id/accountAppBarLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.Account.AppBarLayout">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<android.support.design.widget.CollapsingToolbarLayout
|
||||
android:id="@+id/collapsing_toolbar"
|
||||
android:id="@+id/collapsingToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
android:layout_height="wrap_content"
|
||||
app:contentScrim="?attr/toolbar_background_color"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed"
|
||||
app:statusBarScrim="?android:attr/colorBackground"
|
||||
app:titleEnabled="false">
|
||||
|
||||
<RelativeLayout
|
||||
<ImageView
|
||||
android:id="@+id/accountHeaderImageView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="180dp"
|
||||
android:layout_alignTop="@+id/account_header_info"
|
||||
android:background="?attr/account_header_background_color"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_collapseMode="parallax"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:background="#000" />
|
||||
|
||||
<android.support.constraint.ConstraintLayout
|
||||
android:id="@+id/accountHeaderInfoContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/account_header_background_color">
|
||||
android:layout_marginTop="180dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingStart="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/accountFollowButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Follow" />
|
||||
|
||||
<android.support.text.emoji.widget.EmojiTextView
|
||||
android:id="@+id/accountDisplayNameTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="62dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="?attr/status_text_large"
|
||||
android:textStyle="normal|bold"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Tusky Mastodon Client " />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/accountUsernameTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountDisplayNameTextView"
|
||||
tools:text="\@Tusky" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/account_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignBottom="@+id/account_header_info"
|
||||
android:layout_alignTop="@+id/account_header_info"
|
||||
android:background="@drawable/account_header_default"
|
||||
android:contentDescription="@null"
|
||||
android:fitsSystemWindows="true"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_collapseMode="pin" />
|
||||
android:id="@+id/accountLockedImageView"
|
||||
android:layout_width="16sp"
|
||||
android:layout_height="16sp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:contentDescription="@string/description_account_locked"
|
||||
android:tint="?android:textColorSecondary"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/accountUsernameTextView"
|
||||
app:layout_constraintStart_toEndOf="@+id/accountUsernameTextView"
|
||||
app:layout_constraintTop_toTopOf="@+id/accountUsernameTextView"
|
||||
app:srcCompat="@drawable/reblog_private_light"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<android.support.constraint.ConstraintLayout
|
||||
android:id="@+id/account_header_info"
|
||||
<TextView
|
||||
android:id="@+id/accountFollowsYouTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:background="@drawable/profile_badge_background"
|
||||
android:text="@string/follows_you"
|
||||
android:textSize="?attr/status_text_small"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountUsernameTextView"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/accountBadgeTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:background="@drawable/profile_badge_background"
|
||||
android:text="@string/profile_badge_bot_text"
|
||||
android:textSize="?attr/status_text_small"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toEndOf="@id/accountFollowsYouTextView"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountUsernameTextView"
|
||||
app:layout_goneMarginStart="0dp"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<android.support.constraint.Barrier
|
||||
android:id="@+id/labelBarrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="accountFollowsYouTextView,accountBadgeTextView" />
|
||||
|
||||
<android.support.text.emoji.widget.EmojiTextView
|
||||
android:id="@+id/accountNoteTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/labelBarrier"
|
||||
android:lineSpacingMultiplier="1.1"
|
||||
android:paddingTop="10dp"
|
||||
android:textColor="?android:textColorTertiary"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintTop_toBottomOf="@id/labelBarrier"
|
||||
tools:text="This is a test description. Descriptions can be quite looooong." />
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/accountFieldList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/account_header_gradient"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="?attr/actionBarSize"
|
||||
app:layout_collapseMode="parallax">
|
||||
app:layout_constraintTop_toBottomOf="@id/accountNoteTextView"
|
||||
tools:itemCount="2"
|
||||
tools:listitem="@layout/item_account_field" />
|
||||
|
||||
<com.pkmmte.view.CircularImageView
|
||||
android:id="@+id/account_avatar"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:src="@drawable/avatar_default"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shadow="true" />
|
||||
<ViewStub
|
||||
android:id="@+id/accountMovedView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inflatedId="@+id/accountMovedView"
|
||||
android:layout="@layout/view_account_moved"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountFieldList" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/follow_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
<LinearLayout
|
||||
android:id="@+id/accountFollowers"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintEnd_toStartOf="@id/accountFollowing"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountMovedView">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/account_follows_you"
|
||||
android:id="@+id/accountFollowersTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:text="@string/follows_you"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintEnd_toEndOf="@id/follow_btn"
|
||||
app:layout_constraintTop_toBottomOf="@id/follow_btn" />
|
||||
|
||||
<android.support.text.emoji.widget.EmojiTextView
|
||||
android:id="@+id/account_display_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="?attr/status_text_large"
|
||||
android:textStyle="normal|bold"
|
||||
app:layout_constraintTop_toBottomOf="@id/account_avatar"
|
||||
tools:text="Tusky Mastodon Client" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/account_username"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintTop_toBottomOf="@id/account_display_name"
|
||||
tools:text="\@Tusky" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/account_locked"
|
||||
android:layout_width="16sp"
|
||||
android:layout_height="16sp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:contentDescription="@string/description_account_locked"
|
||||
android:tint="?android:textColorSecondary"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@id/account_username"
|
||||
app:layout_constraintLeft_toRightOf="@id/account_username"
|
||||
app:srcCompat="@drawable/reblog_private_light"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<android.support.text.emoji.widget.EmojiTextView
|
||||
android:id="@+id/account_note"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/account_username"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingTop="10dp"
|
||||
android:textColor="?android:textColorTertiary"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintTop_toBottomOf="@id/account_username"
|
||||
tools:text="This is a test description" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/followers_tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:textColor="@color/account_tab_font_color"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/account_note"
|
||||
tools:text="3000 Followers" />
|
||||
tools:text="1234" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/following_tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="6dp"
|
||||
android:text="@string/title_followers"
|
||||
android:textColor="@color/account_tab_font_color"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintBottom_toBottomOf="@id/followers_tv"
|
||||
app:layout_constraintEnd_toStartOf="@id/statuses_btn"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toEndOf="@id/followers_tv"
|
||||
app:layout_constraintTop_toTopOf="@id/followers_tv"
|
||||
tools:text="500 Following" />
|
||||
android:textSize="?attr/status_text_medium" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/accountFollowing"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintEnd_toStartOf="@id/accountStatuses"
|
||||
app:layout_constraintStart_toEndOf="@id/accountFollowers"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountMovedView">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/statuses_btn"
|
||||
android:id="@+id/accountFollowingTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:textColor="@color/account_tab_font_color"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toEndOf="@id/following_tv"
|
||||
app:layout_constraintTop_toTopOf="@id/followers_tv"
|
||||
tools:text="3000 Posts" />
|
||||
tools:text="500" />
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="6dp"
|
||||
android:text="@string/title_follows"
|
||||
android:textColor="@color/account_tab_font_color"
|
||||
android:textSize="?attr/status_text_medium" />
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/accountStatuses"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/accountFollowing"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountMovedView">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/accountStatusesTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:textColor="@color/account_tab_font_color"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
tools:text="3000" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="6dp"
|
||||
android:text="@string/title_statuses"
|
||||
android:textColor="@color/account_tab_font_color"
|
||||
android:textSize="?attr/status_text_medium" />
|
||||
</LinearLayout>
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
|
||||
<!-- top margin equal to statusbar size will be set programmatically -->
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:id="@+id/accountToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_gravity="top"
|
||||
android:background="@android:color/transparent"
|
||||
app:layout_collapseMode="pin"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
app:popupTheme="?attr/toolbar_popup_theme" />
|
||||
|
||||
</android.support.design.widget.CollapsingToolbarLayout>
|
||||
|
||||
<android.support.design.widget.TabLayout
|
||||
android:id="@+id/accountTabLayout"
|
||||
style="@style/TuskyTabAppearance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:background="?android:colorBackground"
|
||||
app:tabGravity="fill"
|
||||
app:tabIndicatorHeight="3dp"
|
||||
app:tabSelectedTextColor="?attr/colorAccent"
|
||||
app:tabTextAppearance="@style/TuskyTabAppearance" />
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
<android.support.v4.view.ViewPager
|
||||
android:id="@+id/pager"
|
||||
android:id="@+id/accountFragmentViewPager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<android.support.design.widget.TabLayout
|
||||
android:id="@+id/tab_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:colorBackground"
|
||||
app:tabGravity="fill"
|
||||
app:tabIndicatorHeight="3dp"
|
||||
app:tabMaxWidth="0dp"
|
||||
app:tabSelectedTextColor="?attr/colorAccent"
|
||||
app:tabTextAppearance="@style/TuskyTabAppearance">
|
||||
|
||||
<android.support.design.widget.TabItem
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<android.support.design.widget.TabItem
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</android.support.design.widget.TabLayout>
|
||||
|
||||
</android.support.v4.view.ViewPager>
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
<View
|
||||
android:id="@+id/tab_bottom_shadow"
|
||||
android:id="@+id/accountTabBottomShadow"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
android:background="@drawable/material_drawer_shadow_bottom"
|
||||
android:visibility="visible"
|
||||
app:layout_anchor="@id/tab_layout"
|
||||
app:layout_anchor="@id/accountTabLayout"
|
||||
app:layout_anchorGravity="bottom" />
|
||||
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
android:id="@+id/floating_btn"
|
||||
android:id="@+id/accountFloatingActionButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
|
@ -232,6 +298,18 @@
|
|||
android:contentDescription="@string/action_mention"
|
||||
app:srcCompat="@drawable/ic_create_24dp" />
|
||||
|
||||
<include layout="@layout/item_status_bottom_sheet"/>
|
||||
<include layout="@layout/item_status_bottom_sheet" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/accountAvatarImageView"
|
||||
android:layout_width="@dimen/account_activity_avatar_size"
|
||||
android:layout_height="@dimen/account_activity_avatar_size"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="@drawable/avatar_background"
|
||||
android:padding="3dp"
|
||||
app:layout_anchor="@+id/accountHeaderInfoContainer"
|
||||
app:layout_anchorGravity="top"
|
||||
app:layout_scrollFlags="scroll"
|
||||
app:srcCompat="@drawable/avatar_default" />
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
|
|
36
app/src/main/res/layout/item_account_field.xml
Normal file
36
app/src/main/res/layout/item_account_field.xml
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="4dp">
|
||||
|
||||
<android.support.text.emoji.widget.EmojiTextView
|
||||
android:id="@+id/accountFieldName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:lineSpacingMultiplier="1.1"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintEnd_toStartOf="@+id/accountFieldValue"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintWidth_max="160dp"
|
||||
tools:text="Field title " />
|
||||
|
||||
<android.support.text.emoji.widget.EmojiTextView
|
||||
android:id="@+id/accountFieldValue"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:lineSpacingMultiplier="1.1"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/accountFieldName"
|
||||
tools:text="Field content. This can contain links and custom emojis" />
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
58
app/src/main/res/layout/view_account_moved.xml
Normal file
58
app/src/main/res/layout/view_account_moved.xml
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="12dp"
|
||||
android:paddingTop="12dp">
|
||||
|
||||
<android.support.text.emoji.widget.EmojiTextView
|
||||
android:id="@+id/accountMovedText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="6dp"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
tools:text="Account has moved" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/accountMovedAvatar"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginTop="8dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountMovedText"
|
||||
tools:src="@drawable/avatar_default" />
|
||||
|
||||
|
||||
<android.support.text.emoji.widget.EmojiTextView
|
||||
android:id="@+id/accountMovedDisplayName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="?attr/status_text_large"
|
||||
android:textStyle="normal|bold"
|
||||
app:layout_constraintBottom_toTopOf="@id/accountMovedUsername"
|
||||
app:layout_constraintStart_toEndOf="@id/accountMovedAvatar"
|
||||
app:layout_constraintTop_toTopOf="@id/accountMovedAvatar"
|
||||
tools:text="Display name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/accountMovedUsername"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintBottom_toBottomOf="@id/accountMovedAvatar"
|
||||
app:layout_constraintStart_toEndOf="@id/accountMovedAvatar"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountMovedDisplayName"
|
||||
tools:text="\@username" />
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
|
@ -22,4 +22,9 @@
|
|||
android:title="@string/action_block"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item android:id="@+id/action_show_reblogs"
|
||||
android:title="@string/action_hide_reblogs"
|
||||
app:showAsAction="never" />
|
||||
|
||||
|
||||
</menu>
|
|
@ -140,7 +140,6 @@
|
|||
<string name="dialog_title_finishing_media_upload">تتمة رفع الوسائط</string>
|
||||
<string name="dialog_message_uploading_media">جاري الرفع ...</string>
|
||||
<string name="dialog_download_image">تنزيل</string>
|
||||
<string name="dialog_message_follow_request">طلب المتابعة معلق : في إنتظار الرد</string>
|
||||
<string name="dialog_unfollow_warning">هل تود إلغاء متابعة هذا الحساب ؟</string>
|
||||
|
||||
<string name="visibility_public">عمومي : ينشر على الخيوط العمومية</string>
|
||||
|
|
|
@ -132,7 +132,6 @@
|
|||
<string name="dialog_title_finishing_media_upload">S\'està finalitzant la pujada de materila multimèdia</string>
|
||||
<string name="dialog_message_uploading_media">S\'està pujant...</string>
|
||||
<string name="dialog_download_image">Baixa</string>
|
||||
<string name="dialog_message_follow_request">Follow request pending: awaiting their response</string>
|
||||
<string name="dialog_unfollow_warning">Vols deixar de seguir aquest compte?</string>
|
||||
|
||||
<string name="visibility_public">Pública: és visible a la cronologia pública</string>
|
||||
|
|
|
@ -177,7 +177,7 @@
|
|||
<string name="action_view_follow_requests">Folgeanfragen</string>
|
||||
<string name="state_follow_requested">Folgeanfrage gesendet</string>
|
||||
<string name="confirmation_unmuted">Stummschaltung aufgehoben</string>
|
||||
<string name="dialog_message_follow_request">Folgeanfrage gesendet: Warten auf Antwort</string>
|
||||
<string name="dialog_message_cancel_follow_request">Folgeanfrage zurückziehen?</string>
|
||||
<string name="title_edit_profile">Dein Profil bearbeiten</string>
|
||||
<string name="login_connection">Verbinden…</string>
|
||||
|
||||
|
@ -293,5 +293,6 @@
|
|||
<string name="license_blobmoji">Blobmoji ist lizenziert nach der Apache 2.0-Lizenz</string>
|
||||
<string name="license_twemoji">Twemoji ist als CC-BY-4.0 lizenziert - https://creativecommons.org/licenses/by/4.0/</string>
|
||||
<string name="download_failed">Download fehlgeschlagen.</string>
|
||||
<string name="profile_badge_bot_text">Bot</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -140,7 +140,6 @@
|
|||
<string name="dialog_title_finishing_media_upload">Terminando de subir multimedia</string>
|
||||
<string name="dialog_message_uploading_media">Subiendo…</string>
|
||||
<string name="dialog_download_image">Descargar</string>
|
||||
<string name="dialog_message_follow_request">Solicitud enviada: esperando respuesta</string>
|
||||
<string name="dialog_unfollow_warning">¿Dejar de seguir esta cuenta?</string>
|
||||
|
||||
<string name="visibility_public">Público: Mostrar en historias públicas</string>
|
||||
|
|
|
@ -130,7 +130,6 @@
|
|||
<string name="dialog_title_finishing_media_upload">Mise en ligne des médias…</string>
|
||||
<string name="dialog_message_uploading_media">Envoi en cours…</string>
|
||||
<string name="dialog_download_image">Télécharger</string>
|
||||
<string name="dialog_message_follow_request">Demande de suivi en attente de réponse</string>
|
||||
|
||||
<string name="visibility_public">Public : afficher dans les fils publics.</string>
|
||||
<string name="visibility_unlisted">Non listé : ne pas afficher dans les fils publics.</string>
|
||||
|
|
|
@ -130,7 +130,6 @@
|
|||
<string name="dialog_title_finishing_media_upload">Média feltöltés befejezése</string>
|
||||
<string name="dialog_message_uploading_media">Feltöltés…</string>
|
||||
<string name="dialog_download_image">Letöltés</string>
|
||||
<string name="dialog_message_follow_request">Követési kérés függőben: várjuk a válaszát</string>
|
||||
<string name="dialog_unfollow_warning">Követés megszüntetése?</string>
|
||||
|
||||
<string name="visibility_public">Nyilvános: Nyilvános idővonalra való posztolás</string>
|
||||
|
|
|
@ -136,7 +136,6 @@
|
|||
<string name="dialog_title_finishing_media_upload">メディアをアップロードしています</string>
|
||||
<string name="dialog_message_uploading_media">アップロード中…</string>
|
||||
<string name="dialog_download_image">ダウンロード</string>
|
||||
<string name="dialog_message_follow_request">フォローリクエストの承認待ち:返答を待っています</string>
|
||||
<string name="dialog_unfollow_warning">このアカウントをフォロー解除しますか?</string>
|
||||
|
||||
<string name="visibility_public">公開:公開タイムラインに投稿する</string>
|
||||
|
|
|
@ -14,9 +14,6 @@
|
|||
<item name="android:textColorPrimary">@color/text_color_primary_dark</item>
|
||||
<item name="android:textColorSecondary">@color/text_color_secondary_dark</item>
|
||||
<item name="android:textColorTertiary">@color/text_color_tertiary_dark</item>
|
||||
<item name="android:textColorPrimaryInverse">@color/text_color_primary_inverse_dark</item>
|
||||
<item name="android:textColorSecondaryInverse">@color/text_color_secondary_inverse_dark</item>
|
||||
<item name="android:textColorTertiaryInverse">@color/text_color_tertiary_inverse_dark</item>
|
||||
<item name="android:actionMenuTextColor">@color/text_color_primary_dark</item>
|
||||
|
||||
<item name="bottomSheetDialogTheme">@style/AppTheme.BottomSheetDialog.Dark</item>
|
||||
|
@ -39,7 +36,7 @@
|
|||
<item name="conversation_thread_line_drawable">@drawable/conversation_thread_line_dark</item>
|
||||
<item name="tab_icon_selected_tint">@color/color_accent_dark</item>
|
||||
<item name="tab_page_margin_drawable">@drawable/tab_page_margin_dark</item>
|
||||
<item name="account_header_background_color">@color/account_header_background_dark</item>
|
||||
<item name="account_header_background_color">@color/color_background_dark</item>
|
||||
<item name="account_toolbar_icon_tint_uncollapsed">@color/toolbar_icon_dark</item>
|
||||
<item name="account_toolbar_icon_tint_collapsed">@color/account_toolbar_icon_collapsed_dark</item>
|
||||
<item name="toolbar_popup_theme">@style/AppTheme.Account.ToolbarPopupTheme.Dark</item>
|
||||
|
|
|
@ -135,7 +135,6 @@
|
|||
<string name="dialog_title_finishing_media_upload">Media upload beëindigen</string>
|
||||
<string name="dialog_message_uploading_media">Aan het uploaden…</string>
|
||||
<string name="dialog_download_image">Download</string>
|
||||
<string name="dialog_message_follow_request">Verzoek van een volger in de wachtrij: wachtend op hun antwoord</string>
|
||||
<string name="dialog_unfollow_warning">Deze account ontvolgen?</string>
|
||||
|
||||
<string name="visibility_public">Publiek: op publieke tijdlijnen posten</string>
|
||||
|
|
|
@ -144,7 +144,6 @@
|
|||
<string name="dialog_title_finishing_media_upload">Finalizacion del mandadís del mèdia</string>
|
||||
<string name="dialog_message_uploading_media">Mandadís...</string>
|
||||
<string name="dialog_download_image">Telecargar</string>
|
||||
<string name="dialog_message_follow_request">Demanda d’abonament en espèra : responsas esperadas</string>
|
||||
<string name="dialog_unfollow_warning">Volètz quitar de seguir aqueste compte ?</string>
|
||||
|
||||
<string name="visibility_public">Publica : es visibla a la cronologia publica</string>
|
||||
|
|
|
@ -155,7 +155,6 @@
|
|||
<string name="dialog_title_finishing_media_upload">Kończenie wysyłania treści</string>
|
||||
<string name="dialog_message_uploading_media">Wysyłanie…</string>
|
||||
<string name="dialog_download_image">Pobierz</string>
|
||||
<string name="dialog_message_follow_request">Oczekująca prośba o możliwość śledzenia: oczekiwanie na odpowiedź</string>
|
||||
<string name="dialog_unfollow_warning">Czy chcesz przestać śledzić to konto?</string>
|
||||
|
||||
|
||||
|
|
|
@ -133,7 +133,6 @@
|
|||
<string name="dialog_title_finishing_media_upload">Envio de mídia terminando</string>
|
||||
<string name="dialog_message_uploading_media">Enviando…</string>
|
||||
<string name="dialog_download_image">Baixar</string>
|
||||
<string name="dialog_message_follow_request">Solicitação pendente: esperando por resposta</string>
|
||||
<string name="dialog_unfollow_warning">Deixar de seguir esta conta?</string>
|
||||
|
||||
<string name="visibility_public">Público: Postar em timelines públicas</string>
|
||||
|
|
|
@ -144,7 +144,6 @@
|
|||
<string name="dialog_title_finishing_media_upload">Завершается загрузка медиаконтента</string>
|
||||
<string name="dialog_message_uploading_media">Загружается…</string>
|
||||
<string name="dialog_download_image">Скачать</string>
|
||||
<string name="dialog_message_follow_request">Статус запроса на подписку: ожидается ответ</string>
|
||||
<string name="dialog_unfollow_warning">Отписаться от этого аккаунта?</string>
|
||||
|
||||
<string name="visibility_public">Публичный: Показать в публичных лентах</string>
|
||||
|
|
|
@ -129,7 +129,6 @@
|
|||
<string name="dialog_title_finishing_media_upload">மீடியா பதிவேற்றம் முடிகிறது</string>
|
||||
<string name="dialog_message_uploading_media">ஏற்றுகிறது ...</string>
|
||||
<string name="dialog_download_image">பதிவிறக்க</string>
|
||||
<string name="dialog_message_follow_request">கோரிக்கை நிலுவையில் உள்ளது, அவர்களின் பதிலுக்காக காத்திருக்கிறது</string>
|
||||
<string name="dialog_unfollow_warning">இந்த கணக்கை பின்பற்ற வேண்டாமா?</string>
|
||||
|
||||
<string name="visibility_public">அனைவருக்கும் காண்பி</string>
|
||||
|
|
|
@ -115,7 +115,6 @@
|
|||
<string name="dialog_title_finishing_media_upload">Medya Yükleme Bittiriliyor</string>
|
||||
<string name="dialog_message_uploading_media">Yükleniyor…</string>
|
||||
<string name="dialog_download_image">İndir</string>
|
||||
<string name="dialog_message_follow_request">Takip etme istekleri: yanıt bekleniyor</string>
|
||||
|
||||
<string name="visibility_public">Kamu: Herkese açık ve sosyal çizelgelerinde çıkar</string>
|
||||
<string name="visibility_unlisted">Saklı: Herkese açık ancak sosyal çizelgesinde çıkmaz</string>
|
||||
|
|
|
@ -136,7 +136,6 @@
|
|||
<string name="dialog_title_finishing_media_upload">正在结束上传…</string>
|
||||
<string name="dialog_message_uploading_media">正在上传…</string>
|
||||
<string name="dialog_download_image">下载</string>
|
||||
<string name="dialog_message_follow_request">关注请求已发送,等待对方回复</string>
|
||||
<string name="dialog_unfollow_warning">不再关注此用户?</string>
|
||||
|
||||
<string name="visibility_public">公开:所有人可见,并会出现在公共时间轴上</string>
|
||||
|
|
|
@ -136,7 +136,6 @@
|
|||
<string name="dialog_title_finishing_media_upload">正在結束上傳…</string>
|
||||
<string name="dialog_message_uploading_media">正在上傳…</string>
|
||||
<string name="dialog_download_image">下載</string>
|
||||
<string name="dialog_message_follow_request">關注請求已發送,等待對方回覆</string>
|
||||
<string name="dialog_unfollow_warning">不再關注此用户?</string>
|
||||
|
||||
<string name="visibility_public">公開:所有人可見,並會出現在公共時間軸上</string>
|
||||
|
|
|
@ -136,7 +136,6 @@
|
|||
<string name="dialog_title_finishing_media_upload">正在結束上傳…</string>
|
||||
<string name="dialog_message_uploading_media">正在上傳…</string>
|
||||
<string name="dialog_download_image">下載</string>
|
||||
<string name="dialog_message_follow_request">關注請求已發送,等待對方回覆</string>
|
||||
<string name="dialog_unfollow_warning">不再關注此用户?</string>
|
||||
|
||||
<string name="visibility_public">公開:所有人可見,並會出現在公共時間軸上</string>
|
||||
|
|
|
@ -136,7 +136,6 @@
|
|||
<string name="dialog_title_finishing_media_upload">正在结束上传…</string>
|
||||
<string name="dialog_message_uploading_media">正在上传…</string>
|
||||
<string name="dialog_download_image">下载</string>
|
||||
<string name="dialog_message_follow_request">关注请求已发送,等待对方回复</string>
|
||||
<string name="dialog_unfollow_warning">不再关注此用户?</string>
|
||||
|
||||
<string name="visibility_public">公开:所有人可见,并会出现在公共时间轴上</string>
|
||||
|
|
|
@ -136,7 +136,6 @@
|
|||
<string name="dialog_title_finishing_media_upload">正在結束上傳…</string>
|
||||
<string name="dialog_message_uploading_media">正在上傳…</string>
|
||||
<string name="dialog_download_image">下載</string>
|
||||
<string name="dialog_message_follow_request">關注請求已發送,等待對方回覆</string>
|
||||
<string name="dialog_unfollow_warning">不再關注此用戶?</string>
|
||||
|
||||
<string name="visibility_public">公開:所有人可見,並會出現在公共時間軸上</string>
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
<color name="status_divider_dark">#2f3441</color>
|
||||
<color name="tab_page_margin_dark">#1a1c23</color>
|
||||
<color name="account_toolbar_icon_collapsed_dark">#FFFFFF</color>
|
||||
<color name="account_header_background_dark">#000000</color>
|
||||
<color name="compose_media_button_disabled_dark">#586173</color>
|
||||
<color name="compose_mention_dark">#AFBFCF</color>
|
||||
<color name="report_status_background_dark">#000000</color>
|
||||
|
@ -66,7 +65,6 @@
|
|||
<color name="status_divider_light">#cfcfcf</color>
|
||||
<color name="tab_page_margin_light">#cfcfcf</color>
|
||||
<color name="account_toolbar_icon_collapsed_light">#DE000000</color>
|
||||
<color name="account_header_background_light">#EFEFEF</color>
|
||||
<color name="compose_media_button_disabled_light">#a3a5ab</color>
|
||||
<color name="compose_mention_light">#2F5F6F</color>
|
||||
<color name="report_status_background_light">#EFEFEF</color>
|
||||
|
|
|
@ -20,8 +20,10 @@
|
|||
|
||||
<dimen name="compose_activity_snackbar_elevation">16dp</dimen>
|
||||
|
||||
<dimen name="compose_activity_scrollview_height">-1px</dimen> <!-- match_parent -->
|
||||
<dimen name="account_activity_scroll_title_visible_height">200dp</dimen>
|
||||
<dimen name="account_activity_avatar_size">100dp</dimen>
|
||||
|
||||
<dimen name="compose_activity_scrollview_height">-1px</dimen> <!-- match_parent -->
|
||||
<dimen name="timeline_width">-1px</dimen> <!-- match_parent -->
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
<string name="title_view_thread">Toot</string>
|
||||
<string name="title_tag">#%s</string>
|
||||
<string name="title_statuses">Posts</string>
|
||||
<string name="title_statuses_with_replies">With replies</string>
|
||||
<string name="title_follows">Follows</string>
|
||||
<string name="title_followers">Followers</string>
|
||||
<string name="title_favourites">Favourites</string>
|
||||
|
@ -70,6 +71,8 @@
|
|||
<string name="action_unfollow">Unfollow</string>
|
||||
<string name="action_block">Block</string>
|
||||
<string name="action_unblock">Unblock</string>
|
||||
<string name="action_hide_reblogs">Hide boosts</string>
|
||||
<string name="action_show_reblogs">Show boosts</string>
|
||||
<string name="action_report">Report</string>
|
||||
<string name="action_delete">Delete</string>
|
||||
<string name="action_send">TOOT</string>
|
||||
|
@ -94,6 +97,7 @@
|
|||
<string name="action_open_drawer">Open drawer</string>
|
||||
<string name="action_save">Save</string>
|
||||
<string name="action_edit_profile">Edit profile</string>
|
||||
<string name="action_edit_own_profile">Edit</string>
|
||||
<string name="action_undo">Undo</string>
|
||||
<string name="action_accept">Accept</string>
|
||||
<string name="action_reject">Reject</string>
|
||||
|
@ -146,7 +150,7 @@
|
|||
<string name="dialog_title_finishing_media_upload">Finishing Media Upload</string>
|
||||
<string name="dialog_message_uploading_media">Uploading…</string>
|
||||
<string name="dialog_download_image">Download</string>
|
||||
<string name="dialog_message_follow_request">Follow request pending: awaiting their response</string>
|
||||
<string name="dialog_message_cancel_follow_request">Revoke the follow request?</string>
|
||||
<string name="dialog_unfollow_warning">Unfollow this account?</string>
|
||||
|
||||
<string name="visibility_public">Public: Post to public timelines</string>
|
||||
|
@ -334,6 +338,10 @@
|
|||
<string name="license_blobmoji">Blobmoji is licensed under the Apache license 2.0</string>
|
||||
<string name="license_twemoji">Twemoji is licensed as CC-BY 4.0 - https://creativecommons.org/licenses/by/4.0/</string>
|
||||
<string name="download_failed">Download failed</string>
|
||||
|
||||
<string name="profile_badge_bot_text">Bot</string>
|
||||
<string name="account_moved_description">%1$s has moved to:</string>
|
||||
|
||||
<string name="reblog_private">Boost to original audience</string>
|
||||
<string name="unreblog_private">Unboost</string>
|
||||
|
||||
|
|
|
@ -63,11 +63,7 @@
|
|||
<item name="android:textColorPrimary">@color/text_color_primary_light</item>
|
||||
<item name="android:textColorSecondary">@color/text_color_secondary_light</item>
|
||||
<item name="android:textColorTertiary">@color/text_color_tertiary_light</item>
|
||||
<item name="android:textColorPrimaryInverse">@color/text_color_primary_inverse_light</item>
|
||||
<item name="android:textColorSecondaryInverse">@color/text_color_secondary_inverse_light
|
||||
</item>
|
||||
<item name="android:textColorTertiaryInverse">@color/text_color_tertiary_inverse_light
|
||||
</item>
|
||||
|
||||
<item name="android:actionMenuTextColor">@color/text_color_primary_light</item>
|
||||
|
||||
<item name="bottomSheetDialogTheme">@style/AppTheme.BottomSheetDialog.Light</item>
|
||||
|
@ -93,7 +89,7 @@
|
|||
</item>
|
||||
<item name="tab_icon_selected_tint">@color/color_accent_light</item>
|
||||
<item name="tab_page_margin_drawable">@drawable/tab_page_margin_light</item>
|
||||
<item name="account_header_background_color">@color/account_header_background_light</item>
|
||||
<item name="account_header_background_color">@color/color_primary_dark_light</item>
|
||||
<item name="account_toolbar_icon_tint_uncollapsed">@color/toolbar_icon_dark
|
||||
</item> <!--Default to dark on purpose, because header backgrounds with gradients are always dark.-->
|
||||
<item name="account_toolbar_icon_tint_collapsed">
|
||||
|
|
|
@ -57,7 +57,8 @@ class BottomSheetActivityTest {
|
|||
0,
|
||||
0,
|
||||
0,
|
||||
null
|
||||
null,
|
||||
false
|
||||
)
|
||||
private val accountCallback = FakeSearchResults(account)
|
||||
|
||||
|
|
|
@ -255,7 +255,8 @@ class ComposeActivityTest {
|
|||
0,
|
||||
0,
|
||||
0,
|
||||
null
|
||||
null,
|
||||
false
|
||||
),
|
||||
maximumTootCharacters
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue