Compare commits

...

36 Commits

Author SHA1 Message Date
sk
f21b647ee0 bump version 2022-05-21 23:35:10 +02:00
sk
2a628a3791 Merge branch 'feature/back-returns-home' into fork 2022-05-21 23:34:18 +02:00
sk
ecd568503d make back button return home before exiting 2022-05-21 23:33:53 +02:00
sk
4d950e43ac update readme 2022-05-21 18:59:13 +02:00
sk
99405f307d bump version 2022-05-21 18:49:59 +02:00
sk
f1bfe05263 Merge branch 'feature/always-preserve-cw' into fork 2022-05-21 18:49:07 +02:00
sk
0f223159c0 always preserve cw when replying 2022-05-21 18:48:48 +02:00
sk
ad9518e87c re-add deleted strings 2022-05-21 18:08:14 +02:00
sk
1c16cfb09e bump version 2022-05-21 18:05:01 +02:00
sk
d4a4b10017 Merge branch 'feature/compose-image-description-full-image' into fork 2022-05-21 18:03:29 +02:00
sk
74ae5bd04e change app name 2022-05-21 18:03:15 +02:00
sk
9638cf079f set image view height to wrap_content 2022-05-21 17:48:53 +02:00
sk
a6d161c1b4 minor code style change
for grishka's code style must prevail
2022-05-21 17:42:40 +02:00
sk
1136e40eb4 obey image max width 2022-05-21 17:39:28 +02:00
sk
98de3a2984 don't crop image when composing alt text 2022-05-21 17:27:31 +02:00
Grishka
080a320e12 Make the app name non-translatable 2022-05-17 18:47:11 +03:00
sk
b08415ca8f bump version 2022-05-15 20:35:10 +02:00
sk
3639c69d36 Merge branch 'master' into fork 2022-05-15 20:34:14 +02:00
Grishka
37cefcaf6d Fix #164 2022-05-15 21:13:36 +03:00
Grishka
558adc6936 Add compose shortcut
closes #131
2022-05-15 19:14:24 +03:00
sk
31e3a8592f bump version 2022-05-14 13:49:49 +02:00
sk
39655d5278 Merge branch 'master' into fork 2022-05-14 13:48:03 +02:00
Grishka
68d0862008 Close #122 2022-05-13 20:54:22 +03:00
Grishka
c9e13eefa5 Close #146 2022-05-13 20:49:35 +03:00
Grishka
349fbce5af Fix #128 2022-05-13 20:42:54 +03:00
Grishka
95c66654aa Fix #149 2022-05-13 19:20:40 +03:00
Grishka
a8407571a4 Fix #151 2022-05-13 19:18:29 +03:00
Grishka
75538deb9b Fix #156 2022-05-13 19:10:27 +03:00
Grishka
601eec4607 Fix #157 2022-05-13 19:01:29 +03:00
Grishka
9b87d0bece Fix #153 2022-05-13 18:14:52 +03:00
Grishka
cb25632691 Delete statuses from cache and fix auto-refresh when posting 2022-05-13 17:57:41 +03:00
Grishka
63957250c5 Fix #141 + crash fixes 2022-05-13 17:51:28 +03:00
sk
bde2e398a8 bump version and change app name 2022-05-06 22:10:45 +02:00
sk
8d443b2051 Merge branch 'feature/pin-posts' into fork 2022-05-06 21:50:08 +02:00
sk
33d4b678ed update posts' pinned states 2022-05-06 21:49:33 +02:00
sk
3becad1468 fix created posts being added to pinned 2022-05-06 21:18:48 +02:00
31 changed files with 388 additions and 151 deletions

View File

@@ -1,18 +1,19 @@
# Forked Mastodon for Android # Forked Mastodon for Android
[![Crowdin](https://badges.crowdin.net/mastodon-for-android/localized.svg)](https://crowdin.com/project/mastodon-for-android)
This is the repository for an officially forked Android app for Mastodon. This is the repository for an officially forked Android app for Mastodon.
Learn more about this app in the [blog post](https://blog.joinmastodon.org/2022/02/official-mastodon-for-android-app-is-coming-soon/). Learn more about the official app in the [blog post](https://blog.joinmastodon.org/2022/02/official-mastodon-for-android-app-is-coming-soon/).
## Changes ## Changes
* [Enable "Unlisted" as a visibility option](https://github.com/sk22/mastodon-android-fork/tree/feature/enable-unlisted) * [Enable "Unlisted" as a visibility option](https://github.com/sk22/mastodon-android-fork/tree/feature/enable-unlisted)
([Pull request](https://github.com/mastodon/mastodon-android/pull/103)) and ([Pull request](https://github.com/mastodon/mastodon-android/pull/103)) and
[set as default](https://github.com/sk22/mastodon-android-fork/tree/feature/enable-unlisted-as-default) [set as default](https://github.com/sk22/mastodon-android-fork/tree/feature/enable-unlisted-as-default)
* [Add "Federation" tab and change Discover tab order](https://github.com/sk22/mastodon-android-fork/tree/feature/add-federated-timeline) * [Add "Federation" tab and change Discover tab order](https://github.com/sk22/mastodon-android-fork/tree/feature/add-federated-timeline) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/8))
* [Add image description button and viewer](https://github.com/sk22/mastodon-android-fork/tree/feature/display-alt-text) ([Pull request](https://github.com/mastodon/mastodon-android/pull/129)) * [Add image description button and viewer](https://github.com/sk22/mastodon-android-fork/tree/feature/display-alt-text) ([Pull request](https://github.com/mastodon/mastodon-android/pull/129))
* [Implement pinning posts and displaying pinned posts](https://github.com/sk22/mastodon-android-fork/tree/feature/pin-posts) ([Pull request](https://github.com/mastodon/mastodon-android/pull/140)) * [Implement pinning posts and displaying pinned posts](https://github.com/sk22/mastodon-android-fork/tree/feature/pin-posts) ([Pull request](https://github.com/mastodon/mastodon-android/pull/140))
* [Display full image when adding image description](https://github.com/sk22/mastodon-android-fork/tree/feature/compose-image-description-full-image) ([Pull request](https://github.com/mastodon/mastodon-android/pull/182))
* [Always preserve content warnings when replying](https://github.com/sk22/mastodon-android-fork/tree/feature/always-preserve-cw) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/113))
## Building ## Building

View File

@@ -9,8 +9,8 @@ android {
applicationId "org.joinmastodon.android.sk" applicationId "org.joinmastodon.android.sk"
minSdk 23 minSdk 23
targetSdk 31 targetSdk 31
versionCode 7 versionCode 13
versionName '1.1.1-dev+fork.7' versionName '1.1.1+fork.13'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }

View File

@@ -9,6 +9,7 @@ import android.util.Log;
import org.joinmastodon.android.api.ObjectValidationException; import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.ComposeFragment;
import org.joinmastodon.android.fragments.HomeFragment; import org.joinmastodon.android.fragments.HomeFragment;
import org.joinmastodon.android.fragments.ProfileFragment; import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.SplashFragment; import org.joinmastodon.android.fragments.SplashFragment;
@@ -56,6 +57,8 @@ public class MainActivity extends FragmentStackActivity{
if(intent.getBooleanExtra("fromNotification", false) && intent.hasExtra("notification")){ if(intent.getBooleanExtra("fromNotification", false) && intent.hasExtra("notification")){
Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification")); Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
showFragmentForNotification(notification, session.getID()); showFragmentForNotification(notification, session.getID());
}else if(intent.getBooleanExtra("compose", false)){
showCompose();
} }
} }
} }
@@ -91,6 +94,8 @@ public class MainActivity extends FragmentStackActivity{
fragment.setArguments(args); fragment.setArguments(args);
showFragmentClearingBackStack(fragment); showFragmentClearingBackStack(fragment);
} }
}else if(intent.getBooleanExtra("compose", false)){
showCompose();
} }
} }
@@ -115,4 +120,15 @@ public class MainActivity extends FragmentStackActivity{
fragment.setArguments(args); fragment.setArguments(args);
showFragment(fragment); showFragment(fragment);
} }
private void showCompose(){
AccountSession session=AccountSessionManager.getInstance().getLastActiveAccount();
if(session==null || !session.activated)
return;
ComposeFragment compose=new ComposeFragment();
Bundle composeArgs=new Bundle();
composeArgs.putString("account", session.getID());
compose.setArguments(composeArgs);
showFragment(compose);
}
} }

View File

@@ -233,6 +233,12 @@ public class CacheController{
}); });
} }
public void deleteStatus(String id){
runOnDbThread((db)->{
db.delete("home_timeline", "`id`=?", new String[]{id});
});
}
public void clearRecentSearches(){ public void clearRecentSearches(){
runOnDbThread((db)->db.delete("recent_searches", null, null)); runOnDbThread((db)->db.delete("recent_searches", null, null));
} }

View File

@@ -2,15 +2,22 @@ package org.joinmastodon.android.api.session;
import android.app.Activity; import android.app.Activity;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.util.Log; import android.util.Log;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.E; import org.joinmastodon.android.E;
import org.joinmastodon.android.MainActivity;
import org.joinmastodon.android.MastodonApp; import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController; import org.joinmastodon.android.api.MastodonAPIController;
@@ -85,11 +92,12 @@ public class AccountSessionManager{
domains.add(session.domain.toLowerCase()); domains.add(session.domain.toLowerCase());
sessions.put(session.getID(), session); sessions.put(session.getID(), session);
} }
}catch(IOException|JsonParseException x){ }catch(Exception x){
Log.e(TAG, "Error loading accounts", x); Log.e(TAG, "Error loading accounts", x);
} }
lastActiveAccountID=prefs.getString("lastActiveAccount", null); lastActiveAccountID=prefs.getString("lastActiveAccount", null);
MastodonAPIController.runInBackground(()->readInstanceInfo(domains)); MastodonAPIController.runInBackground(()->readInstanceInfo(domains));
maybeUpdateShortcuts();
} }
public void addAccount(Instance instance, Token token, Account self, Application app, boolean active){ public void addAccount(Instance instance, Token token, Account self, Application app, boolean active){
@@ -102,6 +110,7 @@ public class AccountSessionManager{
if(PushSubscriptionManager.arePushNotificationsAvailable()){ if(PushSubscriptionManager.arePushNotificationsAvailable()){
session.getPushSubscriptionManager().registerAccountForPush(null); session.getPushSubscriptionManager().registerAccountForPush(null);
} }
maybeUpdateShortcuts();
} }
public synchronized void writeAccountsFile(){ public synchronized void writeAccountsFile(){
@@ -181,6 +190,7 @@ public class AccountSessionManager{
NotificationManager nm=MastodonApp.context.getSystemService(NotificationManager.class); NotificationManager nm=MastodonApp.context.getSystemService(NotificationManager.class);
nm.deleteNotificationChannelGroup(id); nm.deleteNotificationChannelGroup(id);
} }
maybeUpdateShortcuts();
} }
@NonNull @NonNull
@@ -358,7 +368,7 @@ public class AccountSessionManager{
customEmojis.put(domain, groupCustomEmojis(emojis)); customEmojis.put(domain, groupCustomEmojis(emojis));
instances.put(domain, emojis.instance); instances.put(domain, emojis.instance);
instancesLastUpdated.put(domain, emojis.lastUpdated); instancesLastUpdated.put(domain, emojis.lastUpdated);
}catch(IOException|JsonParseException x){ }catch(Exception x){
Log.w(TAG, "Error reading instance info file for "+domain, x); Log.w(TAG, "Error reading instance info file for "+domain, x);
} }
} }
@@ -395,6 +405,29 @@ public class AccountSessionManager{
writeAccountsFile(); writeAccountsFile();
} }
private void maybeUpdateShortcuts(){
if(Build.VERSION.SDK_INT<26)
return;
ShortcutManager sm=MastodonApp.context.getSystemService(ShortcutManager.class);
if((sm.getDynamicShortcuts().isEmpty() || BuildConfig.DEBUG) && !sessions.isEmpty()){
// There are no shortcuts, but there are accounts. Add a compose shortcut.
ShortcutInfo info=new ShortcutInfo.Builder(MastodonApp.context, "compose")
.setActivity(ComponentName.createRelative(MastodonApp.context, MainActivity.class.getName()))
.setShortLabel(MastodonApp.context.getString(R.string.new_post))
.setIcon(Icon.createWithResource(MastodonApp.context, R.mipmap.ic_shortcut_compose))
.setIntent(new Intent(MastodonApp.context, MainActivity.class)
.setAction(Intent.ACTION_MAIN)
.putExtra("compose", true))
.build();
sm.setDynamicShortcuts(Collections.singletonList(info));
}else if(sessions.isEmpty()){
// There are shortcuts, but no accounts. Disable existing shortcuts.
sm.disableShortcuts(Collections.singletonList("compose"), MastodonApp.context.getString(R.string.err_not_logged_in));
}else{
sm.enableShortcuts(Collections.singletonList("compose"));
}
}
private static class SessionsStorageWrapper{ private static class SessionsStorageWrapper{
public List<AccountSession> accounts; public List<AccountSession> accounts;
} }

View File

@@ -5,7 +5,7 @@ import org.joinmastodon.android.model.Status;
public class StatusCountersUpdatedEvent{ public class StatusCountersUpdatedEvent{
public String id; public String id;
public int favorites, reblogs, replies; public int favorites, reblogs, replies;
public boolean favorited, reblogged; public boolean favorited, reblogged, pinned;
public StatusCountersUpdatedEvent(Status s){ public StatusCountersUpdatedEvent(Status s){
id=s.id; id=s.id;
@@ -14,5 +14,6 @@ public class StatusCountersUpdatedEvent{
replies=s.repliesCount; replies=s.repliesCount;
favorited=s.favourited; favorited=s.favourited;
reblogged=s.reblogged; reblogged=s.reblogged;
pinned=s.pinned;
} }
} }

View File

@@ -78,6 +78,7 @@ public class AccountTimelineFragment extends StatusListFragment{
protected void onStatusCreated(StatusCreatedEvent ev){ protected void onStatusCreated(StatusCreatedEvent ev){
if(!AccountSessionManager.getInstance().isSelf(accountID, ev.status.account)) if(!AccountSessionManager.getInstance().isSelf(accountID, ev.status.account))
return; return;
if(filter==GetAccountStatuses.Filter.PINNED) return;
if(filter==GetAccountStatuses.Filter.DEFAULT){ if(filter==GetAccountStatuses.Filter.DEFAULT){
// Keep replies to self, discard all other replies // Keep replies to self, discard all other replies
if(ev.status.inReplyToAccountId!=null && !ev.status.inReplyToAccountId.equals(AccountSessionManager.getInstance().getAccount(accountID).self.id)) if(ev.status.inReplyToAccountId!=null && !ev.status.inReplyToAccountId.equals(AccountSessionManager.getInstance().getAccount(accountID).self.id))

View File

@@ -461,9 +461,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class); HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
if(header!=null) if(header!=null)
header.rebind(); header.rebind();
for(ImageStatusDisplayItem.Holder photo:(List<ImageStatusDisplayItem.Holder>)findAllHoldersOfType(itemID, ImageStatusDisplayItem.Holder.class)){ updateImagesSpoilerState(status, itemID);
photo.setRevealed(true);
}
} }
public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder){ public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder){
@@ -472,12 +470,25 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
if(!TextUtils.isEmpty(status.spoilerText)){ if(!TextUtils.isEmpty(status.spoilerText)){
TextStatusDisplayItem.Holder text=findHolderOfType(holder.getItemID(), TextStatusDisplayItem.Holder.class); TextStatusDisplayItem.Holder text=findHolderOfType(holder.getItemID(), TextStatusDisplayItem.Holder.class);
if(text!=null){ if(text!=null){
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition()+getMainAdapterOffset()); adapter.notifyItemChanged(text.getAbsoluteAdapterPosition());
} }
} }
holder.rebind(); holder.rebind();
for(ImageStatusDisplayItem.Holder<?> photo:(List<ImageStatusDisplayItem.Holder>)findAllHoldersOfType(holder.getItemID(), ImageStatusDisplayItem.Holder.class)){ updateImagesSpoilerState(status, holder.getItemID());
}
protected void updateImagesSpoilerState(Status status, String itemID){
ArrayList<Integer> updatedPositions=new ArrayList<>();
for(ImageStatusDisplayItem.Holder photo:(List<ImageStatusDisplayItem.Holder>)findAllHoldersOfType(itemID, ImageStatusDisplayItem.Holder.class)){
photo.setRevealed(status.spoilerRevealed); photo.setRevealed(status.spoilerRevealed);
updatedPositions.add(photo.getAbsoluteAdapterPosition()-getMainAdapterOffset());
}
int i=0;
for(StatusDisplayItem item:displayItems){
if(itemID.equals(item.parentID) && item instanceof ImageStatusDisplayItem && !updatedPositions.contains(i)){
adapter.notifyItemChanged(i);
}
i++;
} }
} }

View File

@@ -453,7 +453,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
if(savedInstanceState==null){ if(savedInstanceState==null){
mainEditText.setText(initialText); mainEditText.setText(initialText);
mainEditText.setSelection(mainEditText.length()); mainEditText.setSelection(mainEditText.length());
if(!TextUtils.isEmpty(replyTo.spoilerText) && AccountSessionManager.getInstance().isSelf(accountID, replyTo.account)){ // TODO: setting for preserving cw always / only when replying to own posts
// && AccountSessionManager.getInstance().isSelf(accountID, replyTo.account)
if(!TextUtils.isEmpty(replyTo.spoilerText)){
hasSpoiler=true; hasSpoiler=true;
spoilerEdit.setVisibility(View.VISIBLE); spoilerEdit.setVisibility(View.VISIBLE);
spoilerEdit.setText(replyTo.spoilerText); spoilerEdit.setText(replyTo.spoilerText);
@@ -610,12 +612,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
public void onSuccess(Status result){ public void onSuccess(Status result){
wm.removeView(sendingOverlay); wm.removeView(sendingOverlay);
sendingOverlay=null; sendingOverlay=null;
Nav.finish(ComposeFragment.this);
E.post(new StatusCreatedEvent(result)); E.post(new StatusCreatedEvent(result));
if(replyTo!=null){ if(replyTo!=null){
replyTo.repliesCount++; replyTo.repliesCount++;
E.post(new StatusCountersUpdatedEvent(replyTo)); E.post(new StatusCountersUpdatedEvent(replyTo));
} }
Nav.finish(ComposeFragment.this);
} }
@Override @Override

View File

@@ -255,9 +255,14 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
@Override @Override
public boolean onBackPressed(){ public boolean onBackPressed(){
if(currentTab==R.id.tab_profile) if(currentTab==R.id.tab_profile)
return profileFragment.onBackPressed(); if (profileFragment.onBackPressed()) return true;
if(currentTab==R.id.tab_search) if(currentTab==R.id.tab_search)
return searchFragment.onBackPressed(); if (searchFragment.onBackPressed()) return true;
if (currentTab!=R.id.tab_home) {
tabBar.selectTab(R.id.tab_home);
onTabSelected(R.id.tab_home);
return true;
}
return false; return false;
} }

View File

@@ -515,10 +515,17 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
return; return;
} }
if(relationship==null) if(relationship==null && !isOwnProfile)
return; return;
inflater.inflate(R.menu.profile, menu); inflater.inflate(R.menu.profile, menu);
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getDisplayUsername())); menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getDisplayUsername()));
if(isOwnProfile){
for(int i=0;i<menu.size();i++){
MenuItem item=menu.getItem(i);
item.setVisible(item.getItemId()==R.id.share);
}
return;
}
menu.findItem(R.id.mute).setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getDisplayUsername())); menu.findItem(R.id.mute).setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getDisplayUsername()));
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getDisplayUsername())); menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getDisplayUsername()));
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getDisplayUsername())); menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getDisplayUsername()));

View File

@@ -13,7 +13,6 @@ import org.joinmastodon.android.events.StatusUnpinnedEvent;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.parceler.Parcels; import org.parceler.Parcels;
@@ -116,10 +115,15 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
return; return;
data.remove(status); data.remove(status);
preloadedData.remove(status); preloadedData.remove(status);
HeaderStatusDisplayItem item=findItemOfType(ev.id, HeaderStatusDisplayItem.class); int index=-1;
if(item==null) for(int i=0;i<displayItems.size();i++){
if(ev.id.equals(displayItems.get(i).parentID)){
index=i;
break;
}
}
if(index==-1)
return; return;
int index=displayItems.indexOf(item);
int lastIndex; int lastIndex;
for(lastIndex=index;lastIndex<displayItems.size();lastIndex++){ for(lastIndex=index;lastIndex<displayItems.size();lastIndex++){
if(!displayItems.get(lastIndex).parentID.equals(ev.id)) if(!displayItems.get(lastIndex).parentID.equals(ev.id))

View File

@@ -157,6 +157,18 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
} }
}); });
tabLayoutMediator.attach(); tabLayoutMediator.attach();
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){
@Override
public void onTabSelected(TabLayout.Tab tab){}
@Override
public void onTabUnselected(TabLayout.Tab tab){}
@Override
public void onTabReselected(TabLayout.Tab tab){
scrollToTop();
}
});
searchEdit=view.findViewById(R.id.search_edit); searchEdit=view.findViewById(R.id.search_edit);
searchEdit.setOnFocusChangeListener(this::onSearchEditFocusChanged); searchEdit.setOnFocusChangeListener(this::onSearchEditFocusChanged);

View File

@@ -205,7 +205,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
@Override @Override
public void onTabReselected(TabLayout.Tab tab){ public void onTabReselected(TabLayout.Tab tab){
scrollToTop();
} }
}); });
} }

View File

@@ -126,6 +126,7 @@ public class Status extends BaseModel implements DisplayItemsParent{
repliesCount=ev.replies; repliesCount=ev.replies;
favourited=ev.favorited; favourited=ev.favorited;
reblogged=ev.reblogged; reblogged=ev.reblogged;
pinned=ev.pinned;
} }
public Status getContentStatus(){ public Status getContentStatus(){

View File

@@ -162,6 +162,7 @@ public class ComposeAutocompleteViewController{
.map(WrappedEmoji::new) .map(WrappedEmoji::new)
.collect(Collectors.toList()); .collect(Collectors.toList());
UiUtils.updateList(oldList, emojis, list, emojisAdapter, (e1, e2)->e1.emoji.shortcode.equals(e2.emoji.shortcode)); UiUtils.updateList(oldList, emojis, list, emojisAdapter, (e1, e2)->e1.emoji.shortcode.equals(e2.emoji.shortcode));
imgLoader.updateImages();
} }
} }
@@ -186,6 +187,7 @@ public class ComposeAutocompleteViewController{
List<WrappedAccount> oldList=users; List<WrappedAccount> oldList=users;
users=result.accounts.stream().map(WrappedAccount::new).collect(Collectors.toList()); users=result.accounts.stream().map(WrappedAccount::new).collect(Collectors.toList());
UiUtils.updateList(oldList, users, list, usersAdapter, (a1, a2)->a1.account.id.equals(a2.account.id)); UiUtils.updateList(oldList, users, list, usersAdapter, (a1, a2)->a1.account.id.equals(a2.account.id));
imgLoader.updateImages();
if(listIsHidden){ if(listIsHidden){
listIsHidden=false; listIsHidden=false;
V.setVisibilityAnimated(list, View.VISIBLE); V.setVisibilityAnimated(list, View.VISIBLE);
@@ -210,6 +212,7 @@ public class ComposeAutocompleteViewController{
List<Hashtag> oldList=hashtags; List<Hashtag> oldList=hashtags;
hashtags=result.hashtags; hashtags=result.hashtags;
UiUtils.updateList(oldList, hashtags, list, hashtagsAdapter, (t1, t2)->t1.name.equals(t2.name)); UiUtils.updateList(oldList, hashtags, list, hashtagsAdapter, (t1, t2)->t1.name.equals(t2.name));
imgLoader.updateImages();
if(listIsHidden){ if(listIsHidden){
listIsHidden=false; listIsHidden=false;
V.setVisibilityAnimated(list, View.VISIBLE); V.setVisibilityAnimated(list, View.VISIBLE);

View File

@@ -92,7 +92,9 @@ public class AudioStatusDisplayItem extends StatusDisplayItem{
public void onBind(AudioStatusDisplayItem item){ public void onBind(AudioStatusDisplayItem item){
int seconds=(int)item.attachment.getDuration(); int seconds=(int)item.attachment.getDuration();
String duration=formatDuration(seconds); String duration=formatDuration(seconds);
time.getLayoutParams().width=(int)Math.ceil(time.getPaint().measureText("-"+duration)); // Some fonts (not Roboto) have different-width digits. 0 is supposedly the widest.
time.getLayoutParams().width=(int)Math.ceil(Math.max(time.getPaint().measureText("-"+duration),
time.getPaint().measureText("-"+duration.replaceAll("\\d", "0"))));
time.setText(duration); time.setText(duration);
AudioPlayerService service=AudioPlayerService.getInstance(); AudioPlayerService service=AudioPlayerService.getInstance();
if(service!=null && service.getAttachmentID().equals(item.attachment.id)){ if(service!=null && service.getAttachmentID().equals(item.attachment.id)){

View File

@@ -11,6 +11,7 @@ import android.widget.TextView;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper; import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
import org.joinmastodon.android.ui.views.LinkedTextView; import org.joinmastodon.android.ui.views.LinkedTextView;
@@ -20,7 +21,8 @@ import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
public class TextStatusDisplayItem extends StatusDisplayItem{ public class TextStatusDisplayItem extends StatusDisplayItem{
private CharSequence text; private CharSequence text;
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper(); private CustomEmojiHelper emojiHelper=new CustomEmojiHelper(), spoilerEmojiHelper;
private CharSequence parsedSpoilerText;
public boolean textSelectable; public boolean textSelectable;
public final Status status; public final Status status;
@@ -29,6 +31,11 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
this.text=text; this.text=text;
this.status=status; this.status=status;
emojiHelper.setText(text); emojiHelper.setText(text);
if(!TextUtils.isEmpty(status.spoilerText)){
parsedSpoilerText=HtmlParser.parseCustomEmoji(status.spoilerText, status.emojis);
spoilerEmojiHelper=new CustomEmojiHelper();
spoilerEmojiHelper.setText(parsedSpoilerText);
}
} }
@Override @Override
@@ -38,11 +45,15 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public int getImageCount(){ public int getImageCount(){
if(spoilerEmojiHelper!=null && !status.spoilerRevealed)
return spoilerEmojiHelper.getImageCount();
return emojiHelper.getImageCount(); return emojiHelper.getImageCount();
} }
@Override @Override
public ImageLoaderRequest getImageRequest(int index){ public ImageLoaderRequest getImageRequest(int index){
if(spoilerEmojiHelper!=null && !status.spoilerRevealed)
return spoilerEmojiHelper.getImageRequest(index);
return emojiHelper.getImageRequest(index); return emojiHelper.getImageRequest(index);
} }
@@ -65,7 +76,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
text.setTextIsSelectable(item.textSelectable); text.setTextIsSelectable(item.textSelectable);
text.setInvalidateOnEveryFrame(false); text.setInvalidateOnEveryFrame(false);
if(!TextUtils.isEmpty(item.status.spoilerText)){ if(!TextUtils.isEmpty(item.status.spoilerText)){
spoilerTitle.setText(item.status.spoilerText); spoilerTitle.setText(item.parsedSpoilerText);
if(item.status.spoilerRevealed){ if(item.status.spoilerRevealed){
spoilerOverlay.setVisibility(View.GONE); spoilerOverlay.setVisibility(View.GONE);
text.setVisibility(View.VISIBLE); text.setVisibility(View.VISIBLE);
@@ -84,8 +95,9 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public void setImage(int index, Drawable image){ public void setImage(int index, Drawable image){
item.emojiHelper.setImageDrawable(index, image); getEmojiHelper().setImageDrawable(index, image);
text.invalidate(); text.invalidate();
spoilerTitle.invalidate();
if(image instanceof Animatable){ if(image instanceof Animatable){
((Animatable) image).start(); ((Animatable) image).start();
if(image instanceof MovieDrawable) if(image instanceof MovieDrawable)
@@ -95,8 +107,12 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public void clearImage(int index){ public void clearImage(int index){
item.emojiHelper.setImageDrawable(index, null); getEmojiHelper().setImageDrawable(index, null);
text.invalidate(); text.invalidate();
} }
private CustomEmojiHelper getEmojiHelper(){
return item.spoilerEmojiHelper!=null && !item.status.spoilerRevealed ? item.spoilerEmojiHelper : item.emojiHelper;
}
} }
} }

View File

@@ -0,0 +1,8 @@
package org.joinmastodon.android.ui.text;
/**
* A span to mark character ranges that should be deleted when copied to the clipboard.
* Works with {@link org.joinmastodon.android.ui.views.LinkedTextView}.
*/
public class DeleteWhenCopiedSpan{
}

View File

@@ -67,10 +67,9 @@ public class HtmlParser{
@Override @Override
public void head(@NonNull Node node, int depth){ public void head(@NonNull Node node, int depth){
if(node instanceof TextNode){ if(node instanceof TextNode textNode){
ssb.append(((TextNode) node).text()); ssb.append(textNode.text());
}else if(node instanceof Element){ }else if(node instanceof Element el){
Element el=(Element)node;
switch(el.nodeName()){ switch(el.nodeName()){
case "a" -> { case "a" -> {
String href=el.attr("href"); String href=el.attr("href");
@@ -108,10 +107,9 @@ public class HtmlParser{
@Override @Override
public void tail(@NonNull Node node, int depth){ public void tail(@NonNull Node node, int depth){
if(node instanceof Element){ if(node instanceof Element el){
Element el=(Element)node;
if("span".equals(el.nodeName()) && el.hasClass("ellipsis")){ if("span".equals(el.nodeName()) && el.hasClass("ellipsis")){
ssb.append('…'); ssb.append("", new DeleteWhenCopiedSpan(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}else if("p".equals(el.nodeName())){ }else if("p".equals(el.nodeName())){
if(node.nextSibling()!=null) if(node.nextSibling()!=null)
ssb.append("\n\n"); ssb.append("\n\n");

View File

@@ -43,6 +43,7 @@ import org.joinmastodon.android.api.requests.statuses.DeleteStatus;
import org.joinmastodon.android.api.requests.statuses.GetStatusByID; import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
import org.joinmastodon.android.api.requests.statuses.SetStatusPinned; import org.joinmastodon.android.api.requests.statuses.SetStatusPinned;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusDeletedEvent; import org.joinmastodon.android.events.StatusDeletedEvent;
import org.joinmastodon.android.events.StatusUnpinnedEvent; import org.joinmastodon.android.events.StatusUnpinnedEvent;
import org.joinmastodon.android.fragments.HashtagTimelineFragment; import org.joinmastodon.android.fragments.HashtagTimelineFragment;
@@ -108,7 +109,9 @@ public class UiUtils{
long t=instant.toEpochMilli(); long t=instant.toEpochMilli();
long now=System.currentTimeMillis(); long now=System.currentTimeMillis();
long diff=now-t; long diff=now-t;
if(diff<60_000L){ if(diff<1000L){
return context.getString(R.string.time_now);
}else if(diff<60_000L){
return context.getString(R.string.time_seconds, diff/1000L); return context.getString(R.string.time_seconds, diff/1000L);
}else if(diff<3600_000L){ }else if(diff<3600_000L){
return context.getString(R.string.time_minutes, diff/60_000L); return context.getString(R.string.time_minutes, diff/60_000L);
@@ -337,6 +340,7 @@ public class UiUtils{
@Override @Override
public void onSuccess(Status result){ public void onSuccess(Status result){
resultCallback.accept(result); resultCallback.accept(result);
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().deleteStatus(status.id);
E.post(new StatusDeletedEvent(status.id, accountID)); E.post(new StatusDeletedEvent(status.id, accountID));
} }
@@ -361,6 +365,7 @@ public class UiUtils{
@Override @Override
public void onSuccess(Status result) { public void onSuccess(Status result) {
resultCallback.accept(result); resultCallback.accept(result);
E.post(new StatusCountersUpdatedEvent(result));
if (!result.pinned) if (!result.pinned)
E.post(new StatusUnpinnedEvent(status.id, accountID)); E.post(new StatusUnpinnedEvent(status.id, accountID));
} }

View File

@@ -36,7 +36,7 @@ public class ImageAttachmentFrameLayout extends FrameLayout{
super.onMeasure(widthMeasureSpec, heightMeasureSpec); super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return; return;
} }
int w=Math.min(((View)getParent()).getMeasuredWidth()-horizontalInset, V.dp(MAX_WIDTH)); int w=Math.min(((View)getParent()).getMeasuredWidth(), V.dp(MAX_WIDTH))-horizontalInset;
int actualHeight=Math.round(tile.height/1000f*w)+V.dp(1)*(tile.rowSpan-1); int actualHeight=Math.round(tile.height/1000f*w)+V.dp(1)*(tile.rowSpan-1);
int actualWidth=Math.round(tile.width/1000f*w); int actualWidth=Math.round(tile.width/1000f*w);
if(tile.startCol+tile.colSpan<tileLayout.columnSizes.length) if(tile.startCol+tile.colSpan<tileLayout.columnSizes.length)

View File

@@ -1,36 +1,66 @@
package org.joinmastodon.android.ui.views; package org.joinmastodon.android.ui.views;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context; import android.content.Context;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.widget.TextView; import android.widget.TextView;
import org.joinmastodon.android.ui.text.ClickableLinksDelegate; import org.joinmastodon.android.ui.text.ClickableLinksDelegate;
import org.joinmastodon.android.ui.text.DeleteWhenCopiedSpan;
public class LinkedTextView extends TextView { public class LinkedTextView extends TextView{
private ClickableLinksDelegate delegate=new ClickableLinksDelegate(this); private ClickableLinksDelegate delegate=new ClickableLinksDelegate(this);
private boolean needInvalidate; private boolean needInvalidate;
private ActionMode currentActionMode;
public LinkedTextView(Context context) { public LinkedTextView(Context context){
super(context); this(context, null);
// TODO Auto-generated constructor stub
} }
public LinkedTextView(Context context, AttributeSet attrs) { public LinkedTextView(Context context, AttributeSet attrs){
super(context, attrs); this(context, attrs, 0);
// TODO Auto-generated constructor stub
} }
public LinkedTextView(Context context, AttributeSet attrs, int defStyle) { public LinkedTextView(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle); super(context, attrs, defStyle);
// TODO Auto-generated constructor stub setCustomSelectionActionModeCallback(new ActionMode.Callback(){
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu){
currentActionMode=mode;
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu){
return true;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item){
onTextContextMenuItem(item.getItemId());
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode){
currentActionMode=null;
}
});
} }
public boolean onTouchEvent(MotionEvent ev){ public boolean onTouchEvent(MotionEvent ev){
if(delegate.onTouch(ev)) return true; if(delegate.onTouch(ev)) return true;
return super.onTouchEvent(ev); return super.onTouchEvent(ev);
} }
public void onDraw(Canvas c){ public void onDraw(Canvas c){
@@ -47,4 +77,43 @@ public class LinkedTextView extends TextView {
invalidate(); invalidate();
} }
@Override
public boolean onTextContextMenuItem(int id){
if(id==android.R.id.copy){
final int selStart=getSelectionStart();
final int selEnd=getSelectionEnd();
int min=Math.max(0, Math.min(selStart, selEnd));
int max=Math.max(0, Math.max(selStart, selEnd));
final ClipData copyData=ClipData.newPlainText(null, deleteTextWithinDeleteSpans(getText().subSequence(min, max)));
ClipboardManager clipboard=getContext().getSystemService(ClipboardManager.class);
try {
clipboard.setPrimaryClip(copyData);
} catch (Throwable t) {
Log.w("LinkedTextView", t);
}
if(currentActionMode!=null){
currentActionMode.finish();
}
return true;
}
return super.onTextContextMenuItem(id);
}
private CharSequence deleteTextWithinDeleteSpans(CharSequence text){
if(text instanceof Spanned spanned){
DeleteWhenCopiedSpan[] delSpans=spanned.getSpans(0, text.length(), DeleteWhenCopiedSpan.class);
if(delSpans.length>0){
SpannableStringBuilder ssb=new SpannableStringBuilder(spanned);
for(DeleteWhenCopiedSpan span:delSpans){
int start=ssb.getSpanStart(span);
int end=ssb.getSpanStart(span);
if(start==-1)
continue;
ssb.delete(start, end+1);
}
return ssb;
}
}
return text;
}
} }

View File

@@ -0,0 +1,7 @@
<vector android:height="108dp"
android:viewportHeight="48" android:viewportWidth="48"
android:width="108dp" xmlns:android="http://schemas.android.com/apk/res/android">
<group android:translateX="12" android:translateY="12">
<path android:fillColor="@color/shortcut_icon_foreground" android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</group>
</vector>

View File

@@ -46,6 +46,7 @@
android:textAppearance="@style/m3_label_medium" android:textAppearance="@style/m3_label_medium"
android:textColor="?colorButtonText" android:textColor="?colorButtonText"
android:gravity="end" android:gravity="end"
android:singleLine="true"
tools:text="1:23"/> tools:text="1:23"/>
</LinearLayout> </LinearLayout>

View File

@@ -9,20 +9,21 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<org.joinmastodon.android.ui.views.ComposeMediaLayout <org.joinmastodon.android.ui.views.MaxWidthFrameLayout
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"> android:layout_gravity="center"
android:maxWidth="400dp">
<ImageView <ImageView
android:id="@+id/photo" android:id="@+id/photo"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:scaleType="centerCrop" android:adjustViewBounds="true"
android:importantForAccessibility="no" android:importantForAccessibility="no"
tools:src="#0f0"/> tools:src="#0f0"/>
</org.joinmastodon.android.ui.views.ComposeMediaLayout> </org.joinmastodon.android.ui.views.MaxWidthFrameLayout>
<TextView <TextView
android:id="@+id/title" android:id="@+id/title"

View File

@@ -78,102 +78,110 @@
tools:text="Founder, CEO and lead developer @Mastodon, Germany." /> tools:text="Founder, CEO and lead developer @Mastodon, Germany." />
<LinearLayout <LinearLayout
android:id="@+id/posts_btn" android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_below="@id/bio"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/posts_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_large"
tools:text="123" />
<TextView
android:id="@+id/posts_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_small"
tools:text="following" />
</LinearLayout>
<LinearLayout
android:id="@+id/followers_btn"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_toEndOf="@id/posts_btn"
android:layout_alignTop="@id/posts_btn"
android:layout_marginStart="12dp"
android:orientation="vertical"
android:gravity="center_horizontal">
<TextView
android:id="@+id/followers_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_large"
tools:text="123"/>
<TextView
android:id="@+id/followers_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_small"
tools:text="following"/>
</LinearLayout>
<LinearLayout
android:id="@+id/following_btn"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_alignTop="@id/posts_btn"
android:layout_toEndOf="@id/followers_btn"
android:layout_marginStart="12dp"
android:orientation="vertical"
android:gravity="center_horizontal">
<TextView
android:id="@+id/following_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_large"
tools:text="123"/>
<TextView
android:id="@+id/following_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_small"
tools:text="following"/>
</LinearLayout>
<FrameLayout
android:id="@+id/action_btn_wrap"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentEnd="true" android:layout_below="@id/bio"
android:layout_alignTop="@id/posts_btn" android:orientation="horizontal">
android:layout_marginTop="-8dp"
android:padding="8dp" <LinearLayout
android:layout_marginEnd="8dp" android:id="@+id/posts_btn"
android:clipToPadding="false"> android:layout_width="wrap_content"
<org.joinmastodon.android.ui.views.ProgressBarButton android:layout_height="48dp"
android:id="@+id/action_btn" android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/posts_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_large"
tools:text="123" />
<TextView
android:id="@+id/posts_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_small"
tools:text="following" />
</LinearLayout>
<LinearLayout
android:id="@+id/followers_btn"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginTop="16dp"
android:layout_marginStart="12dp"
android:orientation="vertical"
android:gravity="center_horizontal">
<TextView
android:id="@+id/followers_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_large"
tools:text="123"/>
<TextView
android:id="@+id/followers_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_small"
tools:text="following"/>
</LinearLayout>
<LinearLayout
android:id="@+id/following_btn"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginTop="16dp"
android:layout_marginStart="12dp"
android:orientation="vertical"
android:gravity="center_horizontal">
<TextView
android:id="@+id/following_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_large"
tools:text="123"/>
<TextView
android:id="@+id/following_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_small"
tools:text="following"/>
</LinearLayout>
<Space
android:layout_width="0px"
android:layout_height="1px"
android:layout_weight="1"/>
<FrameLayout
android:id="@+id/action_btn_wrap"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:text="Edit Profile"/> android:layout_marginTop="8dp"
<ProgressBar android:padding="8dp"
android:id="@+id/action_progress" android:layout_marginEnd="8dp"
android:layout_width="wrap_content" android:clipToPadding="false">
android:layout_height="wrap_content" <org.joinmastodon.android.ui.views.ProgressBarButton
android:layout_gravity="center" android:id="@+id/action_btn"
android:indeterminate="true" android:layout_width="wrap_content"
style="?android:progressBarStyleSmall" android:layout_height="wrap_content"
android:elevation="10dp" android:singleLine="true"
android:outlineProvider="none" tools:text="@string/follow_back"/>
android:indeterminateTint="?colorButtonText" <ProgressBar
android:visibility="gone"/> android:id="@+id/action_progress"
</FrameLayout> android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
style="?android:progressBarStyleSmall"
android:elevation="10dp"
android:outlineProvider="none"
android:indeterminateTint="?colorButtonText"
android:visibility="gone"/>
</FrameLayout>
</LinearLayout>
</RelativeLayout> </RelativeLayout>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background>
<shape>
<solid android:color="@color/shortcut_icon_background"/>
<size android:width="108dp" android:height="108dp"/>
</shape>
</background>
<foreground android:drawable="@drawable/ic_compose_foreground"/>
</adaptive-icon>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="shortcut_icon_background">@color/gray_700</color>
<color name="shortcut_icon_foreground">@color/primary_600</color>
</resources>

View File

@@ -93,4 +93,7 @@
<color name="favorite_selected">@color/warning_500</color> <color name="favorite_selected">@color/warning_500</color>
<color name="boost_selected">@color/primary_500</color> <color name="boost_selected">@color/primary_500</color>
<color name="shortcut_icon_background">@color/gray_100</color>
<color name="shortcut_icon_foreground">@color/primary_700</color>
</resources> </resources>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Mastodon</string> <string name="app_name" translatable="false">Mastadon</string>
<string name="get_started">Get started</string> <string name="get_started">Get started</string>
<string name="log_in">Log in</string> <string name="log_in">Log in</string>
@@ -352,4 +352,5 @@
<item quantity="other">%,d reblogs</item> <item quantity="other">%,d reblogs</item>
</plurals> </plurals>
<string name="timestamp_via_app">%1$s via %2$s</string> <string name="timestamp_via_app">%1$s via %2$s</string>
<string name="time_now">now</string>
</resources> </resources>