Compare commits
45 Commits
v1.1.0-dev
...
v1.1.1-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b08415ca8f | ||
|
|
3639c69d36 | ||
|
|
37cefcaf6d | ||
|
|
558adc6936 | ||
|
|
31e3a8592f | ||
|
|
39655d5278 | ||
|
|
68d0862008 | ||
|
|
c9e13eefa5 | ||
|
|
349fbce5af | ||
|
|
95c66654aa | ||
|
|
a8407571a4 | ||
|
|
75538deb9b | ||
|
|
601eec4607 | ||
|
|
9b87d0bece | ||
|
|
cb25632691 | ||
|
|
63957250c5 | ||
|
|
bde2e398a8 | ||
|
|
8d443b2051 | ||
|
|
33d4b678ed | ||
|
|
3becad1468 | ||
|
|
fad3ba3eae | ||
|
|
cb16f95878 | ||
|
|
4e833490ff | ||
|
|
04a973f7b0 | ||
|
|
0318169b74 | ||
|
|
972fb1e241 | ||
|
|
9beb04b01d | ||
|
|
a3bea6ad24 | ||
|
|
7996e4ee4a | ||
|
|
69c4bf4213 | ||
|
|
7cd5ca77f5 | ||
|
|
7e736d3cd3 | ||
|
|
13c2adba56 | ||
|
|
010095a50e | ||
|
|
f0cef2103f | ||
|
|
8ed731a48b | ||
|
|
8660d43cb1 | ||
|
|
0f495f620a | ||
|
|
ac81f10ea8 | ||
|
|
9aa95413e6 | ||
|
|
a0a28a0cb7 | ||
|
|
11d88aed27 | ||
|
|
88504531d4 | ||
|
|
899c9cdf21 | ||
|
|
4ad9fa030b |
@@ -11,6 +11,8 @@ Learn more about this app in the [blog post](https://blog.joinmastodon.org/2022/
|
|||||||
([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)
|
||||||
|
* [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))
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
@@ -22,4 +24,4 @@ As this app is using Java 17 features, you need JDK 17 or newer to build it. Oth
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is released under the [GPL-3 License](./LICENSE).
|
This project is released under the [GPL-3 License](./LICENSE).
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ plugins {
|
|||||||
android {
|
android {
|
||||||
compileSdk 31
|
compileSdk 31
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "org.joinmastodon.android"
|
applicationId "org.joinmastodon.android.sk"
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 31
|
targetSdk 31
|
||||||
versionCode 4
|
versionCode 10
|
||||||
versionName '1.1.0-dev+fork.1.0'
|
versionName '1.1.1-dev+fork.10'
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ public class MastodonErrorResponse extends ErrorResponse{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showToast(Context context){
|
public void showToast(Context context){
|
||||||
|
if(context==null)
|
||||||
|
return;
|
||||||
Toast.makeText(context, error, Toast.LENGTH_SHORT).show();
|
Toast.makeText(context, error, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ public class GetAccountStatuses extends MastodonAPIRequest<List<Status>>{
|
|||||||
switch(filter){
|
switch(filter){
|
||||||
case DEFAULT -> addQueryParameter("exclude_replies", "true");
|
case DEFAULT -> addQueryParameter("exclude_replies", "true");
|
||||||
case INCLUDE_REPLIES -> {}
|
case INCLUDE_REPLIES -> {}
|
||||||
|
case PINNED -> addQueryParameter("pinned", "true");
|
||||||
case MEDIA -> addQueryParameter("only_media", "true");
|
case MEDIA -> addQueryParameter("only_media", "true");
|
||||||
case NO_REBLOGS -> {
|
case NO_REBLOGS -> {
|
||||||
addQueryParameter("exclude_replies", "true");
|
addQueryParameter("exclude_replies", "true");
|
||||||
@@ -32,6 +33,7 @@ public class GetAccountStatuses extends MastodonAPIRequest<List<Status>>{
|
|||||||
public enum Filter{
|
public enum Filter{
|
||||||
DEFAULT,
|
DEFAULT,
|
||||||
INCLUDE_REPLIES,
|
INCLUDE_REPLIES,
|
||||||
|
PINNED,
|
||||||
MEDIA,
|
MEDIA,
|
||||||
NO_REBLOGS
|
NO_REBLOGS
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ public class CreateOAuthApp extends MastodonAPIRequest<Application>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class Request{
|
private static class Request{
|
||||||
public String clientName="Mastodon for Android";
|
public String clientName="Mastodon for Android (Fork)";
|
||||||
public String redirectUris=AccountSessionManager.REDIRECT_URI;
|
public String redirectUris=AccountSessionManager.REDIRECT_URI;
|
||||||
public String scopes=AccountSessionManager.SCOPE;
|
public String scopes=AccountSessionManager.SCOPE;
|
||||||
public String website="https://app.joinmastodon.org/android";
|
public String website="https://github.com/sk22/mastodon-android-fork";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.statuses;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
|
public class SetStatusPinned extends MastodonAPIRequest<Status>{
|
||||||
|
public SetStatusPinned(String id, boolean pinned){
|
||||||
|
super(HttpMethod.POST, "/statuses/"+id+"/"+(pinned ? "pin" : "unpin"), Status.class);
|
||||||
|
setRequestBody(new Object());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.joinmastodon.android.events;
|
||||||
|
|
||||||
|
public class StatusUnpinnedEvent {
|
||||||
|
public final String id;
|
||||||
|
public final String accountID;
|
||||||
|
|
||||||
|
public StatusUnpinnedEvent(String id, String accountID){
|
||||||
|
this.id=id;
|
||||||
|
this.accountID=accountID;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,8 +8,10 @@ import org.joinmastodon.android.R;
|
|||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||||
|
import org.joinmastodon.android.events.StatusUnpinnedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -76,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))
|
||||||
@@ -86,4 +89,24 @@ public class AccountTimelineFragment extends StatusListFragment{
|
|||||||
}
|
}
|
||||||
prependItems(Collections.singletonList(ev.status), true);
|
prependItems(Collections.singletonList(ev.status), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void onStatusUnpinned(StatusUnpinnedEvent ev){
|
||||||
|
if(!ev.accountID.equals(accountID) || filter!=GetAccountStatuses.Filter.PINNED)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Status status=getStatusByID(ev.id);
|
||||||
|
data.remove(status);
|
||||||
|
preloadedData.remove(status);
|
||||||
|
HeaderStatusDisplayItem item=findItemOfType(ev.id, HeaderStatusDisplayItem.class);
|
||||||
|
if(item==null)
|
||||||
|
return;
|
||||||
|
int index=displayItems.indexOf(item);
|
||||||
|
int lastIndex;
|
||||||
|
for(lastIndex=index;lastIndex<displayItems.size();lastIndex++){
|
||||||
|
if(!displayItems.get(lastIndex).parentID.equals(ev.id))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
displayItems.subList(index, lastIndex).clear();
|
||||||
|
adapter.notifyItemRangeRemoved(index, lastIndex-index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -609,17 +609,19 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(Status result){
|
public void onSuccess(Status result){
|
||||||
wm.removeView(sendingOverlay);
|
wm.removeView(sendingOverlay);
|
||||||
Nav.finish(ComposeFragment.this);
|
sendingOverlay=null;
|
||||||
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
|
||||||
public void onError(ErrorResponse error){
|
public void onError(ErrorResponse error){
|
||||||
wm.removeView(sendingOverlay);
|
wm.removeView(sendingOverlay);
|
||||||
|
sendingOverlay=null;
|
||||||
sendProgress.setVisibility(View.GONE);
|
sendProgress.setVisibility(View.GONE);
|
||||||
sendError.setVisibility(View.VISIBLE);
|
sendError.setVisibility(View.VISIBLE);
|
||||||
publishButton.setEnabled(true);
|
publishButton.setEnabled(true);
|
||||||
@@ -647,6 +649,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
confirmDiscardDraftAndFinish();
|
confirmDiscardDraftAndFinish();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if(sendingOverlay!=null)
|
||||||
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
private ProgressBarButton actionButton;
|
private ProgressBarButton actionButton;
|
||||||
private ViewPager2 pager;
|
private ViewPager2 pager;
|
||||||
private NestedRecyclerScrollView scrollView;
|
private NestedRecyclerScrollView scrollView;
|
||||||
private AccountTimelineFragment postsFragment, postsWithRepliesFragment, mediaFragment;
|
private AccountTimelineFragment postsFragment, postsWithRepliesFragment, pinnedPostsFragment, mediaFragment;
|
||||||
private ProfileAboutFragment aboutFragment;
|
private ProfileAboutFragment aboutFragment;
|
||||||
private TabLayout tabbar;
|
private TabLayout tabbar;
|
||||||
private SwipeRefreshLayout refreshLayout;
|
private SwipeRefreshLayout refreshLayout;
|
||||||
@@ -128,6 +128,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
private View fab;
|
private View fab;
|
||||||
private WindowInsets childInsets;
|
private WindowInsets childInsets;
|
||||||
private PhotoViewer currentPhotoViewer;
|
private PhotoViewer currentPhotoViewer;
|
||||||
|
private boolean editModeLoading;
|
||||||
|
|
||||||
public ProfileFragment(){
|
public ProfileFragment(){
|
||||||
super(R.layout.loader_fragment_overlay_toolbar);
|
super(R.layout.loader_fragment_overlay_toolbar);
|
||||||
@@ -208,14 +209,15 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
tabViews=new FrameLayout[4];
|
tabViews=new FrameLayout[5];
|
||||||
for(int i=0;i<tabViews.length;i++){
|
for(int i=0;i<tabViews.length;i++){
|
||||||
FrameLayout tabView=new FrameLayout(getActivity());
|
FrameLayout tabView=new FrameLayout(getActivity());
|
||||||
tabView.setId(switch(i){
|
tabView.setId(switch(i){
|
||||||
case 0 -> R.id.profile_posts;
|
case 0 -> R.id.profile_posts;
|
||||||
case 1 -> R.id.profile_posts_with_replies;
|
case 1 -> R.id.profile_posts_with_replies;
|
||||||
case 2 -> R.id.profile_media;
|
case 2 -> R.id.profile_pinned_posts;
|
||||||
case 3 -> R.id.profile_about;
|
case 3 -> R.id.profile_media;
|
||||||
|
case 4 -> R.id.profile_about;
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+i);
|
default -> throw new IllegalStateException("Unexpected value: "+i);
|
||||||
});
|
});
|
||||||
tabView.setVisibility(View.GONE);
|
tabView.setVisibility(View.GONE);
|
||||||
@@ -223,7 +225,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
tabViews[i]=tabView;
|
tabViews[i]=tabView;
|
||||||
}
|
}
|
||||||
|
|
||||||
pager.setOffscreenPageLimit(4);
|
pager.setOffscreenPageLimit(5);
|
||||||
pager.setAdapter(new ProfilePagerAdapter());
|
pager.setAdapter(new ProfilePagerAdapter());
|
||||||
pager.getLayoutParams().height=getResources().getDisplayMetrics().heightPixels;
|
pager.getLayoutParams().height=getResources().getDisplayMetrics().heightPixels;
|
||||||
|
|
||||||
@@ -239,8 +241,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
tab.setText(switch(position){
|
tab.setText(switch(position){
|
||||||
case 0 -> R.string.posts;
|
case 0 -> R.string.posts;
|
||||||
case 1 -> R.string.posts_and_replies;
|
case 1 -> R.string.posts_and_replies;
|
||||||
case 2 -> R.string.media;
|
case 2 -> R.string.pinned_posts;
|
||||||
case 3 -> R.string.profile_about;
|
case 3 -> R.string.media;
|
||||||
|
case 4 -> R.string.profile_about;
|
||||||
default -> throw new IllegalStateException();
|
default -> throw new IllegalStateException();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -297,6 +300,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
postsFragment.onRefresh();
|
postsFragment.onRefresh();
|
||||||
if(postsWithRepliesFragment.loaded)
|
if(postsWithRepliesFragment.loaded)
|
||||||
postsWithRepliesFragment.onRefresh();
|
postsWithRepliesFragment.onRefresh();
|
||||||
|
if(pinnedPostsFragment.loaded)
|
||||||
|
pinnedPostsFragment.onRefresh();
|
||||||
if(mediaFragment.loaded)
|
if(mediaFragment.loaded)
|
||||||
mediaFragment.onRefresh();
|
mediaFragment.onRefresh();
|
||||||
}
|
}
|
||||||
@@ -321,6 +326,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
if(postsFragment==null){
|
if(postsFragment==null){
|
||||||
postsFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.DEFAULT, true);
|
postsFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.DEFAULT, true);
|
||||||
postsWithRepliesFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.INCLUDE_REPLIES, false);
|
postsWithRepliesFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.INCLUDE_REPLIES, false);
|
||||||
|
pinnedPostsFragment =AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.PINNED, false);
|
||||||
mediaFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.MEDIA, false);
|
mediaFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.MEDIA, false);
|
||||||
aboutFragment=new ProfileAboutFragment();
|
aboutFragment=new ProfileAboutFragment();
|
||||||
aboutFragment.setFields(fields);
|
aboutFragment.setFields(fields);
|
||||||
@@ -401,6 +407,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
if(postsFragment!=null && postsFragment.isAdded() && childInsets!=null){
|
if(postsFragment!=null && postsFragment.isAdded() && childInsets!=null){
|
||||||
postsFragment.onApplyWindowInsets(childInsets);
|
postsFragment.onApplyWindowInsets(childInsets);
|
||||||
postsWithRepliesFragment.onApplyWindowInsets(childInsets);
|
postsWithRepliesFragment.onApplyWindowInsets(childInsets);
|
||||||
|
pinnedPostsFragment.onApplyWindowInsets(childInsets);
|
||||||
mediaFragment.onApplyWindowInsets(childInsets);
|
mediaFragment.onApplyWindowInsets(childInsets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -508,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()));
|
||||||
@@ -636,8 +650,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
return switch(page){
|
return switch(page){
|
||||||
case 0 -> postsFragment;
|
case 0 -> postsFragment;
|
||||||
case 1 -> postsWithRepliesFragment;
|
case 1 -> postsWithRepliesFragment;
|
||||||
case 2 -> mediaFragment;
|
case 2 -> pinnedPostsFragment;
|
||||||
case 3 -> aboutFragment;
|
case 3 -> mediaFragment;
|
||||||
|
case 4 -> aboutFragment;
|
||||||
default -> throw new IllegalStateException();
|
default -> throw new IllegalStateException();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -664,17 +679,26 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loadAccountInfoAndEnterEditMode(){
|
private void loadAccountInfoAndEnterEditMode(){
|
||||||
|
if(editModeLoading)
|
||||||
|
return;
|
||||||
|
editModeLoading=true;
|
||||||
setActionProgressVisible(true);
|
setActionProgressVisible(true);
|
||||||
new GetOwnAccount()
|
new GetOwnAccount()
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Account result){
|
public void onSuccess(Account result){
|
||||||
|
editModeLoading=false;
|
||||||
|
if(getActivity()==null)
|
||||||
|
return;
|
||||||
enterEditMode(result);
|
enterEditMode(result);
|
||||||
setActionProgressVisible(false);
|
setActionProgressVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error){
|
public void onError(ErrorResponse error){
|
||||||
|
editModeLoading=false;
|
||||||
|
if(getActivity()==null)
|
||||||
|
return;
|
||||||
error.showToast(getActivity());
|
error.showToast(getActivity());
|
||||||
setActionProgressVisible(false);
|
setActionProgressVisible(false);
|
||||||
}
|
}
|
||||||
@@ -689,9 +713,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
pager.setUserInputEnabled(false);
|
pager.setUserInputEnabled(false);
|
||||||
actionButton.setText(R.string.done);
|
actionButton.setText(R.string.done);
|
||||||
pager.setCurrentItem(3);
|
pager.setCurrentItem(4);
|
||||||
ArrayList<Animator> animators=new ArrayList<>();
|
ArrayList<Animator> animators=new ArrayList<>();
|
||||||
for(int i=0;i<3;i++){
|
for(int i=0;i<tabViews.length-1;i++){
|
||||||
animators.add(ObjectAnimator.ofFloat(tabbar.getTabAt(i).view, View.ALPHA, .3f));
|
animators.add(ObjectAnimator.ofFloat(tabbar.getTabAt(i).view, View.ALPHA, .3f));
|
||||||
tabbar.getTabAt(i).view.setEnabled(false);
|
tabbar.getTabAt(i).view.setEnabled(false);
|
||||||
}
|
}
|
||||||
@@ -732,7 +756,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
ArrayList<Animator> animators=new ArrayList<>();
|
ArrayList<Animator> animators=new ArrayList<>();
|
||||||
actionButton.setText(R.string.edit_profile);
|
actionButton.setText(R.string.edit_profile);
|
||||||
for(int i=0;i<3;i++){
|
for(int i=0;i<tabViews.length-1;i++){
|
||||||
animators.add(ObjectAnimator.ofFloat(tabbar.getTabAt(i).view, View.ALPHA, 1f));
|
animators.add(ObjectAnimator.ofFloat(tabbar.getTabAt(i).view, View.ALPHA, 1f));
|
||||||
}
|
}
|
||||||
animators.add(ObjectAnimator.ofInt(avatar.getForeground(), "alpha", 0));
|
animators.add(ObjectAnimator.ofInt(avatar.getForeground(), "alpha", 0));
|
||||||
@@ -750,7 +774,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
set.addListener(new AnimatorListenerAdapter(){
|
set.addListener(new AnimatorListenerAdapter(){
|
||||||
@Override
|
@Override
|
||||||
public void onAnimationEnd(Animator animation){
|
public void onAnimationEnd(Animator animation){
|
||||||
for(int i=0;i<3;i++){
|
for(int i=0;i<tabViews.length-1;i++){
|
||||||
tabbar.getTabAt(i).view.setEnabled(true);
|
tabbar.getTabAt(i).view.setEnabled(true);
|
||||||
}
|
}
|
||||||
pager.setUserInputEnabled(true);
|
pager.setUserInputEnabled(true);
|
||||||
@@ -927,7 +951,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount(){
|
public int getItemCount(){
|
||||||
return loaded ? 4 : 0;
|
return loaded ? tabViews.length : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ public interface ScrollableToTop{
|
|||||||
* @param list
|
* @param list
|
||||||
*/
|
*/
|
||||||
default void smoothScrollRecyclerViewToTop(RecyclerView list){
|
default void smoothScrollRecyclerViewToTop(RecyclerView list){
|
||||||
|
if(list==null) // TODO find out why this happens because it should not be possible
|
||||||
|
return;
|
||||||
if(list.getChildCount()>0 && list.getChildAdapterPosition(list.getChildAt(0))>10){
|
if(list.getChildCount()>0 && list.getChildAdapterPosition(list.getChildAt(0))>10){
|
||||||
list.scrollToPosition(0);
|
list.scrollToPosition(0);
|
||||||
list.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
list.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import org.joinmastodon.android.events.PollUpdatedEvent;
|
|||||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||||
import org.joinmastodon.android.events.StatusDeletedEvent;
|
import org.joinmastodon.android.events.StatusDeletedEvent;
|
||||||
|
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;
|
||||||
|
|
||||||
@@ -61,6 +61,8 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
|||||||
|
|
||||||
protected void onStatusCreated(StatusCreatedEvent ev){}
|
protected void onStatusCreated(StatusCreatedEvent ev){}
|
||||||
|
|
||||||
|
protected void onStatusUnpinned(StatusUnpinnedEvent ev){}
|
||||||
|
|
||||||
protected Status getContentStatusByID(String id){
|
protected Status getContentStatusByID(String id){
|
||||||
Status s=getStatusByID(id);
|
Status s=getStatusByID(id);
|
||||||
return s==null ? null : s.getContentStatus();
|
return s==null ? null : s.getContentStatus();
|
||||||
@@ -113,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))
|
||||||
@@ -131,6 +138,11 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
|||||||
StatusListFragment.this.onStatusCreated(ev);
|
StatusListFragment.this.onStatusCreated(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onStatusUnpinned(StatusUnpinnedEvent ev){
|
||||||
|
StatusListFragment.this.onStatusUnpinned(ev);
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void onPollUpdated(PollUpdatedEvent ev){
|
public void onPollUpdated(PollUpdatedEvent ev){
|
||||||
if(!ev.accountID.equals(accountID))
|
if(!ev.accountID.equals(accountID))
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import org.joinmastodon.android.GlobalUserPreferences;
|
|||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
@@ -234,7 +235,7 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
|||||||
|
|
||||||
public void bindRelationship(){
|
public void bindRelationship(){
|
||||||
Relationship rel=relationships.get(item.account.id);
|
Relationship rel=relationships.get(item.account.id);
|
||||||
if(rel==null){
|
if(rel==null || AccountSessionManager.getInstance().isSelf(accountID, item.account)){
|
||||||
button.setVisibility(View.GONE);
|
button.setVisibility(View.GONE);
|
||||||
}else{
|
}else{
|
||||||
button.setVisibility(View.VISIBLE);
|
button.setVisibility(View.VISIBLE);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -117,6 +117,8 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
|||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
if(isInRecentMode()){
|
if(isInRecentMode()){
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().getRecentSearches(sr->{
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().getRecentSearches(sr->{
|
||||||
|
if(getActivity()==null)
|
||||||
|
return;
|
||||||
unfilteredResults=sr;
|
unfilteredResults=sr;
|
||||||
prevDisplayItems=new ArrayList<>(displayItems);
|
prevDisplayItems=new ArrayList<>(displayItems);
|
||||||
onDataLoaded(sr, false);
|
onDataLoaded(sr, false);
|
||||||
@@ -203,7 +205,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTabReselected(TabLayout.Tab tab){
|
public void onTabReselected(TabLayout.Tab tab){
|
||||||
|
scrollToTop();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(){
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package org.joinmastodon.android.ui;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.graphics.drawable.ColorDrawable;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.WindowInsets;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.model.Attachment;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
|
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
import me.grishka.appkit.views.BottomSheet;
|
||||||
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
|
public class ImageDescriptionSheet extends BottomSheet{
|
||||||
|
private UsableRecyclerView list;
|
||||||
|
|
||||||
|
public ImageDescriptionSheet(@NonNull Activity activity, Attachment attachment){
|
||||||
|
super(activity);
|
||||||
|
|
||||||
|
View handleView=new View(activity);
|
||||||
|
handleView.setBackgroundResource(R.drawable.bg_bottom_sheet_handle);
|
||||||
|
ViewGroup handle=new FrameLayout(activity);
|
||||||
|
handle.addView(handleView);
|
||||||
|
handle.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(24)));
|
||||||
|
|
||||||
|
TextView textView = new TextView(activity);
|
||||||
|
if (attachment.description == null || attachment.description.isEmpty()) {
|
||||||
|
textView.setText(R.string.media_no_description);
|
||||||
|
textView.setTypeface(null, Typeface.ITALIC);
|
||||||
|
} else {
|
||||||
|
textView.setText(attachment.description);
|
||||||
|
textView.setTextIsSelectable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextView heading=new TextView(activity);
|
||||||
|
heading.setText(R.string.image_description);
|
||||||
|
heading.setAllCaps(true);
|
||||||
|
heading.setTypeface(null, Typeface.BOLD);
|
||||||
|
heading.setPadding(0, V.dp(24), 0, V.dp(8));
|
||||||
|
|
||||||
|
LinearLayout linearLayout = new LinearLayout(activity);
|
||||||
|
linearLayout.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
linearLayout.setPadding(V.dp(24), 0, V.dp(24), 0);
|
||||||
|
linearLayout.addView(heading);
|
||||||
|
linearLayout.addView(textView);
|
||||||
|
|
||||||
|
FrameLayout layout=new FrameLayout(activity);
|
||||||
|
layout.addView(handle);
|
||||||
|
layout.addView(linearLayout);
|
||||||
|
|
||||||
|
list=new UsableRecyclerView(activity);
|
||||||
|
list.setLayoutManager(new LinearLayoutManager(activity));
|
||||||
|
list.setBackgroundResource(R.drawable.bg_bottom_sheet);
|
||||||
|
list.setAdapter(new SingleViewRecyclerAdapter(layout));
|
||||||
|
list.setClipToPadding(false);
|
||||||
|
|
||||||
|
setContentView(list);
|
||||||
|
setNavigationBarBackground(new ColorDrawable(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground)), !UiUtils.isDarkTheme());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onWindowInsetsUpdated(WindowInsets insets){
|
||||||
|
if(Build.VERSION.SDK_INT>=29){
|
||||||
|
int tappableBottom=insets.getTappableElementInsets().bottom;
|
||||||
|
int insetBottom=insets.getSystemWindowInsetBottom();
|
||||||
|
if(tappableBottom==0 && insetBottom>0){
|
||||||
|
list.setPadding(0, 0, 0, V.dp(48)-insetBottom);
|
||||||
|
}else{
|
||||||
|
list.setPadding(0, 0, 0, V.dp(24));
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
list.setPadding(0, 0, 0, V.dp(24));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)){
|
||||||
|
|||||||
@@ -137,6 +137,8 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
int id=menuItem.getItemId();
|
int id=menuItem.getItemId();
|
||||||
if(id==R.id.delete){
|
if(id==R.id.delete){
|
||||||
UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{});
|
UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{});
|
||||||
|
}else if(id==R.id.pin || id==R.id.unpin){
|
||||||
|
UiUtils.confirmPinPost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, !item.status.pinned, s->{});
|
||||||
}else if(id==R.id.mute){
|
}else if(id==R.id.mute){
|
||||||
UiUtils.confirmToggleMuteUser(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), account, relationship!=null && relationship.muting, r->{});
|
UiUtils.confirmToggleMuteUser(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), account, relationship!=null && relationship.muting, r->{});
|
||||||
}else if(id==R.id.block){
|
}else if(id==R.id.block){
|
||||||
@@ -250,6 +252,8 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
Menu menu=optionsMenu.getMenu();
|
Menu menu=optionsMenu.getMenu();
|
||||||
boolean isOwnPost=AccountSessionManager.getInstance().isSelf(item.parentFragment.getAccountID(), account);
|
boolean isOwnPost=AccountSessionManager.getInstance().isSelf(item.parentFragment.getAccountID(), account);
|
||||||
menu.findItem(R.id.delete).setVisible(item.status!=null && isOwnPost);
|
menu.findItem(R.id.delete).setVisible(item.status!=null && isOwnPost);
|
||||||
|
menu.findItem(R.id.pin).setVisible(item.status!=null && isOwnPost && !item.status.pinned);
|
||||||
|
menu.findItem(R.id.unpin).setVisible(item.status!=null && isOwnPost && item.status.pinned);
|
||||||
menu.findItem(R.id.open_in_browser).setVisible(item.status!=null);
|
menu.findItem(R.id.open_in_browser).setVisible(item.status!=null);
|
||||||
MenuItem blockDomain=menu.findItem(R.id.block_domain);
|
MenuItem blockDomain=menu.findItem(R.id.block_domain);
|
||||||
MenuItem mute=menu.findItem(R.id.mute);
|
MenuItem mute=menu.findItem(R.id.mute);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import android.media.AudioManager;
|
|||||||
import android.media.MediaPlayer;
|
import android.media.MediaPlayer;
|
||||||
import android.media.MediaScannerConnection;
|
import android.media.MediaScannerConnection;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.opengl.Visibility;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
@@ -48,6 +49,7 @@ import android.widget.Toolbar;
|
|||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.MastodonAPIController;
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
import org.joinmastodon.android.model.Attachment;
|
import org.joinmastodon.android.model.Attachment;
|
||||||
|
import org.joinmastodon.android.ui.ImageDescriptionSheet;
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -97,6 +99,7 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
|||||||
private TextView videoTimeView;
|
private TextView videoTimeView;
|
||||||
private ImageButton videoPlayPauseButton;
|
private ImageButton videoPlayPauseButton;
|
||||||
private View videoControls;
|
private View videoControls;
|
||||||
|
private MenuItem imageDescriptionButton;
|
||||||
private boolean uiVisible=true;
|
private boolean uiVisible=true;
|
||||||
private AudioManager.OnAudioFocusChangeListener audioFocusListener=this::onAudioFocusChanged;
|
private AudioManager.OnAudioFocusChangeListener audioFocusListener=this::onAudioFocusChanged;
|
||||||
private Runnable uiAutoHider=()->{
|
private Runnable uiAutoHider=()->{
|
||||||
@@ -174,11 +177,24 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
|||||||
toolbarWrap=uiOverlay.findViewById(R.id.toolbar_wrap);
|
toolbarWrap=uiOverlay.findViewById(R.id.toolbar_wrap);
|
||||||
toolbar=uiOverlay.findViewById(R.id.toolbar);
|
toolbar=uiOverlay.findViewById(R.id.toolbar);
|
||||||
toolbar.setNavigationOnClickListener(v->onStartSwipeToDismissTransition(0));
|
toolbar.setNavigationOnClickListener(v->onStartSwipeToDismissTransition(0));
|
||||||
toolbar.getMenu().add(R.string.download).setIcon(R.drawable.ic_fluent_arrow_download_24_regular).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
imageDescriptionButton = toolbar.getMenu()
|
||||||
toolbar.setOnMenuItemClickListener(item->{
|
.add(R.string.image_description)
|
||||||
saveCurrentFile();
|
.setIcon(R.drawable.ic_fluent_image_alt_text_24_regular)
|
||||||
return true;
|
.setVisible(attachments.get(pager.getCurrentItem()).description != null
|
||||||
});
|
&& !attachments.get(pager.getCurrentItem()).description.isEmpty())
|
||||||
|
.setOnMenuItemClickListener(item -> {
|
||||||
|
new ImageDescriptionSheet(activity,attachments.get(pager.getCurrentItem())).show();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
imageDescriptionButton.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||||
|
toolbar.getMenu()
|
||||||
|
.add(R.string.download)
|
||||||
|
.setIcon(R.drawable.ic_fluent_arrow_download_24_regular)
|
||||||
|
.setOnMenuItemClickListener(item -> {
|
||||||
|
saveCurrentFile();
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||||
uiOverlay.setAlpha(0f);
|
uiOverlay.setAlpha(0f);
|
||||||
videoControls=uiOverlay.findViewById(R.id.video_player_controls);
|
videoControls=uiOverlay.findViewById(R.id.video_player_controls);
|
||||||
videoSeekBar=uiOverlay.findViewById(R.id.seekbar);
|
videoSeekBar=uiOverlay.findViewById(R.id.seekbar);
|
||||||
@@ -374,6 +390,8 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
|||||||
private void onPageChanged(int index){
|
private void onPageChanged(int index){
|
||||||
currentIndex=index;
|
currentIndex=index;
|
||||||
Attachment att=attachments.get(index);
|
Attachment att=attachments.get(index);
|
||||||
|
imageDescriptionButton.setVisible(att.description != null && !att.description.isEmpty());
|
||||||
|
toolbar.invalidate();
|
||||||
V.setVisibilityAnimated(videoControls, att.type==Attachment.Type.VIDEO ? View.VISIBLE : View.GONE);
|
V.setVisibilityAnimated(videoControls, att.type==Attachment.Type.VIDEO ? View.VISIBLE : View.GONE);
|
||||||
if(att.type==Attachment.Type.VIDEO){
|
if(att.type==Attachment.Type.VIDEO){
|
||||||
videoSeekBar.setSecondaryProgress(0);
|
videoSeekBar.setSecondaryProgress(0);
|
||||||
|
|||||||
@@ -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{
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.joinmastodon.android.ui.utils;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.ColorStateList;
|
import android.content.res.ColorStateList;
|
||||||
@@ -10,7 +11,6 @@ import android.content.res.TypedArray;
|
|||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Typeface;
|
|
||||||
import android.graphics.drawable.BitmapDrawable;
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.graphics.drawable.InsetDrawable;
|
import android.graphics.drawable.InsetDrawable;
|
||||||
@@ -29,6 +29,7 @@ import android.webkit.MimeTypeMap;
|
|||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.PopupMenu;
|
import android.widget.PopupMenu;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
@@ -40,8 +41,11 @@ import org.joinmastodon.android.api.requests.accounts.SetAccountMuted;
|
|||||||
import org.joinmastodon.android.api.requests.accounts.SetDomainBlocked;
|
import org.joinmastodon.android.api.requests.accounts.SetDomainBlocked;
|
||||||
import org.joinmastodon.android.api.requests.statuses.DeleteStatus;
|
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.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.fragments.HashtagTimelineFragment;
|
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||||
@@ -87,13 +91,17 @@ public class UiUtils{
|
|||||||
private UiUtils(){}
|
private UiUtils(){}
|
||||||
|
|
||||||
public static void launchWebBrowser(Context context, String url){
|
public static void launchWebBrowser(Context context, String url){
|
||||||
if(GlobalUserPreferences.useCustomTabs){
|
try{
|
||||||
new CustomTabsIntent.Builder()
|
if(GlobalUserPreferences.useCustomTabs){
|
||||||
.setShowTitle(true)
|
new CustomTabsIntent.Builder()
|
||||||
.build()
|
.setShowTitle(true)
|
||||||
.launchUrl(context, Uri.parse(url));
|
.build()
|
||||||
}else{
|
.launchUrl(context, Uri.parse(url));
|
||||||
context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
|
}else{
|
||||||
|
context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
|
||||||
|
}
|
||||||
|
}catch(ActivityNotFoundException x){
|
||||||
|
Toast.makeText(context, R.string.no_app_to_handle_action, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,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);
|
||||||
@@ -330,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));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,6 +354,32 @@ public class UiUtils{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void confirmPinPost(Activity activity, String accountID, Status status, boolean pinned, Consumer<Status> resultCallback){
|
||||||
|
showConfirmationAlert(activity,
|
||||||
|
pinned ? R.string.confirm_pin_post_title : R.string.confirm_unpin_post_title,
|
||||||
|
pinned ? R.string.confirm_pin_post : R.string.confirm_unpin_post,
|
||||||
|
pinned ? R.string.pin_post : R.string.unpin_post,
|
||||||
|
()->{
|
||||||
|
new SetStatusPinned(status.id, pinned)
|
||||||
|
.setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Status result) {
|
||||||
|
resultCallback.accept(result);
|
||||||
|
E.post(new StatusCountersUpdatedEvent(result));
|
||||||
|
if (!result.pinned)
|
||||||
|
E.post(new StatusUnpinnedEvent(status.id, accountID));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
error.showToast(activity);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.wrapProgress(activity, pinned ? R.string.pinning : R.string.unpinning, false)
|
||||||
|
.exec(accountID);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public static void setRelationshipToActionButton(Relationship relationship, Button button){
|
public static void setRelationshipToActionButton(Relationship relationship, Button button){
|
||||||
boolean secondaryStyle;
|
boolean secondaryStyle;
|
||||||
if(relationship.blocking){
|
if(relationship.blocking){
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -1,38 +1,68 @@
|
|||||||
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) {
|
|
||||||
super(context);
|
public LinkedTextView(Context context){
|
||||||
// TODO Auto-generated constructor stub
|
this(context, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
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){
|
||||||
super.onDraw(c);
|
super.onDraw(c);
|
||||||
delegate.onDraw(c);
|
delegate.onDraw(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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
mastodon/src/main/res/drawable/ic_compose_foreground.xml
Normal file
7
mastodon/src/main/res/drawable/ic_compose_foreground.xml
Normal 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>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M1 3c0-1.105 0.895-2 2-2h7c1.105 0 2 0.895 2 2v6c0 1.105-0.895 2-2 2H3c-1.105 0-2-0.895-2-2V3zm2.5 1C3.224 4 3 4.224 3 4.5S3.224 5 3.5 5h6C9.776 5 10 4.776 10 4.5S9.776 4 9.5 4h-6zm0 3C3.224 7 3 7.224 3 7.5S3.224 8 3.5 8h6C9.776 8 10 7.776 10 7.5S9.776 7 9.5 7h-6zM4 12h1.5v6.75c0 0.208 0.036 0.408 0.103 0.594l5.823-5.701c0.833-0.816 2.142-0.854 3.02-0.116l0.128 0.116 5.822 5.702c0.067-0.186 0.104-0.386 0.104-0.595V7.25c0-0.966-0.784-1.75-1.75-1.75H13V4h5.75C20.545 4 22 5.455 22 7.25v11.5c0 1.795-1.455 3.25-3.25 3.25H7.25C5.455 22 4 20.545 4 18.75V12zm15.33 8.401l-5.805-5.686c-0.265-0.26-0.675-0.283-0.966-0.071l-0.084 0.07-5.807 5.687C6.85 20.465 7.046 20.5 7.25 20.5h11.5c0.203 0 0.399-0.035 0.58-0.099zM16.253 7.5c1.244 0 2.252 1.008 2.252 2.252 0 1.244-1.008 2.252-2.252 2.252-1.244 0-2.252-1.008-2.252-2.252C14 8.508 15.008 7.5 16.252 7.5zm0 1.5C15.837 9 15.5 9.337 15.5 9.752s0.337 0.752 0.752 0.752c0.416 0 0.752-0.336 0.752-0.752C17.004 9.337 16.667 9 16.252 9z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:id="@+id/delete" android:title="@string/delete"/>
|
<item android:id="@+id/delete" android:title="@string/delete"/>
|
||||||
|
<item android:id="@+id/pin" android:title="@string/pin_post"/>
|
||||||
|
<item android:id="@+id/unpin" android:title="@string/unpin_post"/>
|
||||||
<item android:id="@+id/mute" android:title="@string/mute_user"/>
|
<item android:id="@+id/mute" android:title="@string/mute_user"/>
|
||||||
<item android:id="@+id/block" android:title="@string/block_user"/>
|
<item android:id="@+id/block" android:title="@string/block_user"/>
|
||||||
<item android:id="@+id/block_domain" android:title="@string/block_domain"/>
|
<item android:id="@+id/block_domain" android:title="@string/block_domain"/>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -42,6 +42,7 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<string name="posts">Beiträge</string>
|
<string name="posts">Beiträge</string>
|
||||||
<string name="posts_and_replies">Beiträge und Antworten</string>
|
<string name="posts_and_replies">Beiträge und Antworten</string>
|
||||||
|
<string name="pinned_posts">Angeheftet</string>
|
||||||
<string name="media">Medien</string>
|
<string name="media">Medien</string>
|
||||||
<string name="profile_about">Über</string>
|
<string name="profile_about">Über</string>
|
||||||
<string name="button_follow">Folgen</string>
|
<string name="button_follow">Folgen</string>
|
||||||
@@ -124,6 +125,14 @@
|
|||||||
<string name="confirm_delete_title">Beitrag löschen</string>
|
<string name="confirm_delete_title">Beitrag löschen</string>
|
||||||
<string name="confirm_delete">Bist du dir sicher, dass du den Beitrag löschen möchtest?</string>
|
<string name="confirm_delete">Bist du dir sicher, dass du den Beitrag löschen möchtest?</string>
|
||||||
<string name="deleting">Wird gelöscht…</string>
|
<string name="deleting">Wird gelöscht…</string>
|
||||||
|
<string name="pin_post">An Profil anheften</string>
|
||||||
|
<string name="confirm_pin_post_title">Beitrag an Profil anheften</string>
|
||||||
|
<string name="confirm_pin_post">Möchtest du den Beitrag an dein Profil anheften?</string>
|
||||||
|
<string name="pinning">Wird angeheftet…</string>
|
||||||
|
<string name="unpin_post">Von Profil lösen</string>
|
||||||
|
<string name="confirm_unpin_post_title">Angehefteten Beitrag von Profil lösen</string>
|
||||||
|
<string name="confirm_unpin_post">Bist du dir sicher, dass du den angehefteten Beitrag von deinem Profil lösen möchtest?</string>
|
||||||
|
<string name="unpinning">Wird vom Profil gelöst…</string>
|
||||||
<string name="notification_channel_audio_player">Audiowiedergabe</string>
|
<string name="notification_channel_audio_player">Audiowiedergabe</string>
|
||||||
<string name="play">Abspielen</string>
|
<string name="play">Abspielen</string>
|
||||||
<string name="pause">Pausieren</string>
|
<string name="pause">Pausieren</string>
|
||||||
@@ -203,6 +212,7 @@
|
|||||||
<string name="resent_email">Bestätigungs-E-Mail gesendet</string>
|
<string name="resent_email">Bestätigungs-E-Mail gesendet</string>
|
||||||
<string name="compose_hint">Was gibt\'s Neues?</string>
|
<string name="compose_hint">Was gibt\'s Neues?</string>
|
||||||
<string name="content_warning">Inhaltswarnung</string>
|
<string name="content_warning">Inhaltswarnung</string>
|
||||||
|
<string name="image_description">Bildbeschreibung</string>
|
||||||
<string name="add_image_description">Füge eine Bildbeschreibung hinzu…</string>
|
<string name="add_image_description">Füge eine Bildbeschreibung hinzu…</string>
|
||||||
<string name="retry_upload">Upload erneut versuchen</string>
|
<string name="retry_upload">Upload erneut versuchen</string>
|
||||||
<string name="image_upload_failed">Fehler beim Hochladen des Bildes</string>
|
<string name="image_upload_failed">Fehler beim Hochladen des Bildes</string>
|
||||||
|
|||||||
5
mastodon/src/main/res/values-night/colors.xml
Normal file
5
mastodon/src/main/res/values-night/colors.xml
Normal 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>
|
||||||
@@ -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>
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
<item name="profile_posts" type="id"/>
|
<item name="profile_posts" type="id"/>
|
||||||
<item name="profile_posts_with_replies" type="id"/>
|
<item name="profile_posts_with_replies" type="id"/>
|
||||||
|
<item name="profile_pinned_posts" type="id"/>
|
||||||
<item name="profile_media" type="id"/>
|
<item name="profile_media" type="id"/>
|
||||||
<item name="profile_about" type="id"/>
|
<item name="profile_about" type="id"/>
|
||||||
|
|
||||||
|
|||||||
@@ -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">Mastođon</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>
|
||||||
@@ -47,6 +47,7 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<string name="posts">Posts</string>
|
<string name="posts">Posts</string>
|
||||||
<string name="posts_and_replies">Posts and Replies</string>
|
<string name="posts_and_replies">Posts and Replies</string>
|
||||||
|
<string name="pinned_posts">Pinned</string>
|
||||||
<string name="media">Media</string>
|
<string name="media">Media</string>
|
||||||
<string name="profile_about">About</string>
|
<string name="profile_about">About</string>
|
||||||
<string name="button_follow">Follow</string>
|
<string name="button_follow">Follow</string>
|
||||||
@@ -129,6 +130,14 @@
|
|||||||
<string name="confirm_delete_title">Delete Post</string>
|
<string name="confirm_delete_title">Delete Post</string>
|
||||||
<string name="confirm_delete">Are you sure you want to delete this post?</string>
|
<string name="confirm_delete">Are you sure you want to delete this post?</string>
|
||||||
<string name="deleting">Deleting…</string>
|
<string name="deleting">Deleting…</string>
|
||||||
|
<string name="pin_post">Pin to profile</string>
|
||||||
|
<string name="confirm_pin_post_title">Pin post to profile</string>
|
||||||
|
<string name="confirm_pin_post">Do you want to pin this post to your profile?</string>
|
||||||
|
<string name="pinning">Pinning post…</string>
|
||||||
|
<string name="unpin_post">Unpin from profile</string>
|
||||||
|
<string name="confirm_unpin_post_title">Unpin post from profile</string>
|
||||||
|
<string name="confirm_unpin_post">Are you sure you want to unpin this post?</string>
|
||||||
|
<string name="unpinning">Unpinning post…</string>
|
||||||
<string name="notification_channel_audio_player">Audio playback</string>
|
<string name="notification_channel_audio_player">Audio playback</string>
|
||||||
<string name="play">Play</string>
|
<string name="play">Play</string>
|
||||||
<string name="pause">Pause</string>
|
<string name="pause">Pause</string>
|
||||||
@@ -209,6 +218,7 @@
|
|||||||
<string name="compose_hint">Type or paste what\'s on your mind</string>
|
<string name="compose_hint">Type or paste what\'s on your mind</string>
|
||||||
<string name="content_warning">Content warning</string>
|
<string name="content_warning">Content warning</string>
|
||||||
<string name="add_image_description">Add image description…</string>
|
<string name="add_image_description">Add image description…</string>
|
||||||
|
<string name="image_description">Image description</string>
|
||||||
<string name="retry_upload">Retry upload</string>
|
<string name="retry_upload">Retry upload</string>
|
||||||
<string name="image_upload_failed">Image failed to upload</string>
|
<string name="image_upload_failed">Image failed to upload</string>
|
||||||
<string name="video_upload_failed">Video failed to upload</string>
|
<string name="video_upload_failed">Video failed to upload</string>
|
||||||
@@ -342,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>
|
||||||
Reference in New Issue
Block a user