Compare commits
1 Commits
v2.2.1
...
mastodon-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a168a0226b |
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||||
|
patreon: mastodon
|
||||||
|
open_collective: # Replace with a single Open Collective username e.g., user1
|
||||||
|
ko_fi: # Replace with a single Ko-fi username e.g., user1
|
||||||
|
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
|
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
|
liberapay: # Replace with a single Liberapay username e.g., user1
|
||||||
|
issuehunt: # Replace with a single IssueHunt username e.g., user1
|
||||||
|
otechie: # Replace with a single Otechie username e.g., user1
|
||||||
|
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||||
@@ -9,8 +9,8 @@ android {
|
|||||||
applicationId "org.joinmastodon.android"
|
applicationId "org.joinmastodon.android"
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 76
|
versionCode 66
|
||||||
versionName "2.2.1"
|
versionName "2.1.0"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "da-rDK", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fa-rIR", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "ig-rNG", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "kab", "ko-rKR", "my-rMM", "nl-rNL", "no-rNO", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "ur-rIN", "vi-rVN", "zh-rCN", "zh-rTW"
|
resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "da-rDK", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fa-rIR", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "ig-rNG", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "kab", "ko-rKR", "my-rMM", "nl-rNL", "no-rNO", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "ur-rIN", "vi-rVN", "zh-rCN", "zh-rTW"
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@ dependencies {
|
|||||||
implementation 'me.grishka.litex:viewpager:1.0.0'
|
implementation 'me.grishka.litex:viewpager:1.0.0'
|
||||||
implementation 'me.grishka.litex:viewpager2:1.0.0'
|
implementation 'me.grishka.litex:viewpager2:1.0.0'
|
||||||
implementation 'me.grishka.litex:palette:1.0.0'
|
implementation 'me.grishka.litex:palette:1.0.0'
|
||||||
implementation 'me.grishka.appkit:appkit:1.2.15'
|
implementation 'me.grishka.appkit:appkit:1.2.10'
|
||||||
implementation 'com.google.code.gson:gson:2.8.9'
|
implementation 'com.google.code.gson:gson:2.8.9'
|
||||||
implementation 'org.jsoup:jsoup:1.14.3'
|
implementation 'org.jsoup:jsoup:1.14.3'
|
||||||
implementation 'com.squareup:otto:1.3.8'
|
implementation 'com.squareup:otto:1.3.8'
|
||||||
|
|||||||
@@ -42,7 +42,40 @@ public class MainActivity extends FragmentStackActivity{
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
if(savedInstanceState==null){
|
if(savedInstanceState==null){
|
||||||
restartHomeFragment();
|
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
|
||||||
|
showFragmentClearingBackStack(new SplashFragment());
|
||||||
|
}else{
|
||||||
|
AccountSessionManager.getInstance().maybeUpdateLocalInfo();
|
||||||
|
AccountSession session;
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
Intent intent=getIntent();
|
||||||
|
if(intent.getBooleanExtra("fromNotification", false)){
|
||||||
|
String accountID=intent.getStringExtra("accountID");
|
||||||
|
try{
|
||||||
|
session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
|
if(!intent.hasExtra("notification"))
|
||||||
|
args.putString("tab", "notifications");
|
||||||
|
}catch(IllegalStateException x){
|
||||||
|
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
||||||
|
}
|
||||||
|
args.putString("account", session.getID());
|
||||||
|
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
|
||||||
|
fragment.setArguments(args);
|
||||||
|
showFragmentClearingBackStack(fragment);
|
||||||
|
if(intent.getBooleanExtra("fromNotification", false) && intent.hasExtra("notification")){
|
||||||
|
Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
|
||||||
|
showFragmentForNotification(notification, session.getID());
|
||||||
|
}else if(intent.getBooleanExtra("compose", false)){
|
||||||
|
showCompose();
|
||||||
|
}else if(Intent.ACTION_VIEW.equals(intent.getAction())){
|
||||||
|
handleURL(intent.getData(), null);
|
||||||
|
}else{
|
||||||
|
maybeRequestNotificationsPermission();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(BuildConfig.BUILD_TYPE.startsWith("appcenter")){
|
if(BuildConfig.BUILD_TYPE.startsWith("appcenter")){
|
||||||
@@ -167,41 +200,4 @@ public class MainActivity extends FragmentStackActivity{
|
|||||||
requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 100);
|
requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void restartHomeFragment(){
|
|
||||||
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
|
|
||||||
showFragmentClearingBackStack(new SplashFragment());
|
|
||||||
}else{
|
|
||||||
AccountSessionManager.getInstance().maybeUpdateLocalInfo();
|
|
||||||
AccountSession session;
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
Intent intent=getIntent();
|
|
||||||
if(intent.getBooleanExtra("fromNotification", false)){
|
|
||||||
String accountID=intent.getStringExtra("accountID");
|
|
||||||
try{
|
|
||||||
session=AccountSessionManager.getInstance().getAccount(accountID);
|
|
||||||
if(!intent.hasExtra("notification"))
|
|
||||||
args.putString("tab", "notifications");
|
|
||||||
}catch(IllegalStateException x){
|
|
||||||
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
|
||||||
}
|
|
||||||
args.putString("account", session.getID());
|
|
||||||
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
|
|
||||||
fragment.setArguments(args);
|
|
||||||
showFragmentClearingBackStack(fragment);
|
|
||||||
if(intent.getBooleanExtra("fromNotification", false) && intent.hasExtra("notification")){
|
|
||||||
Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
|
|
||||||
showFragmentForNotification(notification, session.getID());
|
|
||||||
}else if(intent.getBooleanExtra("compose", false)){
|
|
||||||
showCompose();
|
|
||||||
}else if(Intent.ACTION_VIEW.equals(intent.getAction())){
|
|
||||||
handleURL(intent.getData(), null);
|
|
||||||
}else{
|
|
||||||
maybeRequestNotificationsPermission();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,8 +118,6 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
List<NotificationChannel> channels=Arrays.stream(PushNotification.Type.values())
|
List<NotificationChannel> channels=Arrays.stream(PushNotification.Type.values())
|
||||||
.map(type->{
|
.map(type->{
|
||||||
NotificationChannel channel=new NotificationChannel(accountID+"_"+type, context.getString(type.localizedName), NotificationManager.IMPORTANCE_DEFAULT);
|
NotificationChannel channel=new NotificationChannel(accountID+"_"+type, context.getString(type.localizedName), NotificationManager.IMPORTANCE_DEFAULT);
|
||||||
channel.setLightColor(context.getColor(R.color.primary_700));
|
|
||||||
channel.enableLights(true);
|
|
||||||
channel.setGroup(accountID);
|
channel.setGroup(accountID);
|
||||||
return channel;
|
return channel;
|
||||||
})
|
})
|
||||||
@@ -149,7 +147,6 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
.setShowWhen(true)
|
.setShowWhen(true)
|
||||||
.setCategory(Notification.CATEGORY_SOCIAL)
|
.setCategory(Notification.CATEGORY_SOCIAL)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setLights(context.getColor(R.color.primary_700), 500, 1000)
|
|
||||||
.setColor(context.getColor(R.color.primary_700));
|
.setColor(context.getColor(R.color.primary_700));
|
||||||
if(avatar!=null){
|
if(avatar!=null){
|
||||||
builder.setLargeIcon(UiUtils.getBitmapFromDrawable(avatar));
|
builder.setLargeIcon(UiUtils.getBitmapFromDrawable(avatar));
|
||||||
|
|||||||
@@ -9,31 +9,20 @@ import android.os.Handler;
|
|||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.BuildConfig;
|
import org.joinmastodon.android.BuildConfig;
|
||||||
import org.joinmastodon.android.MastodonApp;
|
import org.joinmastodon.android.MastodonApp;
|
||||||
import org.joinmastodon.android.api.requests.lists.GetLists;
|
|
||||||
import org.joinmastodon.android.api.requests.notifications.GetNotifications;
|
import org.joinmastodon.android.api.requests.notifications.GetNotifications;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||||
import org.joinmastodon.android.model.FilterContext;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.FollowList;
|
|
||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
import org.joinmastodon.android.model.PaginatedResponse;
|
import org.joinmastodon.android.model.PaginatedResponse;
|
||||||
import org.joinmastodon.android.model.SearchResult;
|
import org.joinmastodon.android.model.SearchResult;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@@ -53,7 +42,6 @@ public class CacheController{
|
|||||||
private final Runnable databaseCloseRunnable=this::closeDatabase;
|
private final Runnable databaseCloseRunnable=this::closeDatabase;
|
||||||
private boolean loadingNotifications;
|
private boolean loadingNotifications;
|
||||||
private final ArrayList<Callback<PaginatedResponse<List<Notification>>>> pendingNotificationsCallbacks=new ArrayList<>();
|
private final ArrayList<Callback<PaginatedResponse<List<Notification>>>> pendingNotificationsCallbacks=new ArrayList<>();
|
||||||
private List<FollowList> lists;
|
|
||||||
|
|
||||||
private static final int POST_FLAG_GAP_AFTER=1;
|
private static final int POST_FLAG_GAP_AFTER=1;
|
||||||
|
|
||||||
@@ -312,99 +300,6 @@ public class CacheController{
|
|||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reloadLists(Callback<List<FollowList>> callback){
|
|
||||||
new GetLists()
|
|
||||||
.setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(List<FollowList> result){
|
|
||||||
result.sort(Comparator.comparing(l->l.title));
|
|
||||||
lists=result;
|
|
||||||
if(callback!=null)
|
|
||||||
callback.onSuccess(result);
|
|
||||||
writeListsToFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
if(callback!=null)
|
|
||||||
callback.onError(error);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec(accountID);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<FollowList> loadListsFromFile(){
|
|
||||||
File file=getListsFile();
|
|
||||||
if(!file.exists())
|
|
||||||
return null;
|
|
||||||
try(InputStreamReader in=new InputStreamReader(new FileInputStream(file))){
|
|
||||||
return MastodonAPIController.gson.fromJson(in, new TypeToken<List<FollowList>>(){}.getType());
|
|
||||||
}catch(Exception x){
|
|
||||||
Log.w(TAG, "failed to read lists from cache file", x);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeListsToFile(){
|
|
||||||
databaseThread.postRunnable(()->{
|
|
||||||
try(OutputStreamWriter out=new OutputStreamWriter(new FileOutputStream(getListsFile()))){
|
|
||||||
MastodonAPIController.gson.toJson(lists, out);
|
|
||||||
}catch(IOException x){
|
|
||||||
Log.w(TAG, "failed to write lists to cache file", x);
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void getLists(Callback<List<FollowList>> callback){
|
|
||||||
if(lists!=null){
|
|
||||||
if(callback!=null)
|
|
||||||
callback.onSuccess(lists);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
databaseThread.postRunnable(()->{
|
|
||||||
List<FollowList> lists=loadListsFromFile();
|
|
||||||
if(lists!=null){
|
|
||||||
this.lists=lists;
|
|
||||||
if(callback!=null)
|
|
||||||
uiHandler.post(()->callback.onSuccess(lists));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
reloadLists(callback);
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public File getListsFile(){
|
|
||||||
return new File(MastodonApp.context.getFilesDir(), "lists_"+accountID+".json");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addList(FollowList list){
|
|
||||||
if(lists==null)
|
|
||||||
return;
|
|
||||||
lists.add(list);
|
|
||||||
lists.sort(Comparator.comparing(l->l.title));
|
|
||||||
writeListsToFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deleteList(String id){
|
|
||||||
if(lists==null)
|
|
||||||
return;
|
|
||||||
lists.removeIf(l->l.id.equals(id));
|
|
||||||
writeListsToFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateList(FollowList list){
|
|
||||||
if(lists==null)
|
|
||||||
return;
|
|
||||||
for(int i=0;i<lists.size();i++){
|
|
||||||
if(lists.get(i).id.equals(list.id)){
|
|
||||||
lists.set(i, list);
|
|
||||||
lists.sort(Comparator.comparing(l->l.title));
|
|
||||||
writeListsToFile();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class DatabaseHelper extends SQLiteOpenHelper{
|
private class DatabaseHelper extends SQLiteOpenHelper{
|
||||||
|
|
||||||
public DatabaseHelper(){
|
public DatabaseHelper(){
|
||||||
|
|||||||
@@ -92,15 +92,15 @@ public class MastodonAPIController{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(BuildConfig.DEBUG)
|
if(BuildConfig.DEBUG)
|
||||||
Log.d(TAG, logTag(session)+"Sending request: "+hreq);
|
Log.d(TAG, "["+(session==null ? "no-auth" : session.getID())+"] Sending request: "+hreq);
|
||||||
|
|
||||||
call.enqueue(new Callback(){
|
call.enqueue(new Callback(){
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(@NonNull Call call, @NonNull IOException e){
|
public void onFailure(@NonNull Call call, @NonNull IOException e){
|
||||||
if(req.canceled)
|
if(call.isCanceled())
|
||||||
return;
|
return;
|
||||||
if(BuildConfig.DEBUG)
|
if(BuildConfig.DEBUG)
|
||||||
Log.w(TAG, logTag(session)+""+hreq+" failed", e);
|
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+hreq+" failed", e);
|
||||||
synchronized(req){
|
synchronized(req){
|
||||||
req.okhttpCall=null;
|
req.okhttpCall=null;
|
||||||
}
|
}
|
||||||
@@ -109,10 +109,10 @@ public class MastodonAPIController{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException{
|
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException{
|
||||||
if(req.canceled)
|
if(call.isCanceled())
|
||||||
return;
|
return;
|
||||||
if(BuildConfig.DEBUG)
|
if(BuildConfig.DEBUG)
|
||||||
Log.d(TAG, logTag(session)+hreq+" received response: "+response);
|
Log.d(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+hreq+" received response: "+response);
|
||||||
synchronized(req){
|
synchronized(req){
|
||||||
req.okhttpCall=null;
|
req.okhttpCall=null;
|
||||||
}
|
}
|
||||||
@@ -123,7 +123,7 @@ public class MastodonAPIController{
|
|||||||
try{
|
try{
|
||||||
if(BuildConfig.DEBUG){
|
if(BuildConfig.DEBUG){
|
||||||
JsonElement respJson=JsonParser.parseReader(reader);
|
JsonElement respJson=JsonParser.parseReader(reader);
|
||||||
Log.d(TAG, logTag(session)+"response body: "+respJson);
|
Log.d(TAG, "["+(session==null ? "no-auth" : session.getID())+"] response body: "+respJson);
|
||||||
if(req.respTypeToken!=null)
|
if(req.respTypeToken!=null)
|
||||||
respObj=gson.fromJson(respJson, req.respTypeToken.getType());
|
respObj=gson.fromJson(respJson, req.respTypeToken.getType());
|
||||||
else if(req.respClass!=null)
|
else if(req.respClass!=null)
|
||||||
@@ -140,7 +140,7 @@ public class MastodonAPIController{
|
|||||||
}
|
}
|
||||||
}catch(JsonIOException|JsonSyntaxException x){
|
}catch(JsonIOException|JsonSyntaxException x){
|
||||||
if(BuildConfig.DEBUG)
|
if(BuildConfig.DEBUG)
|
||||||
Log.w(TAG, logTag(session)+response+" error parsing or reading body", x);
|
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" error parsing or reading body", x);
|
||||||
req.onError(x.getLocalizedMessage(), response.code(), x);
|
req.onError(x.getLocalizedMessage(), response.code(), x);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -149,19 +149,19 @@ public class MastodonAPIController{
|
|||||||
req.validateAndPostprocessResponse(respObj, response);
|
req.validateAndPostprocessResponse(respObj, response);
|
||||||
}catch(IOException x){
|
}catch(IOException x){
|
||||||
if(BuildConfig.DEBUG)
|
if(BuildConfig.DEBUG)
|
||||||
Log.w(TAG, logTag(session)+response+" error post-processing or validating response", x);
|
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" error post-processing or validating response", x);
|
||||||
req.onError(x.getLocalizedMessage(), response.code(), x);
|
req.onError(x.getLocalizedMessage(), response.code(), x);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(BuildConfig.DEBUG)
|
if(BuildConfig.DEBUG)
|
||||||
Log.d(TAG, logTag(session)+response+" parsed successfully: "+respObj);
|
Log.d(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" parsed successfully: "+respObj);
|
||||||
|
|
||||||
req.onSuccess(respObj);
|
req.onSuccess(respObj);
|
||||||
}else{
|
}else{
|
||||||
try{
|
try{
|
||||||
JsonObject error=JsonParser.parseReader(reader).getAsJsonObject();
|
JsonObject error=JsonParser.parseReader(reader).getAsJsonObject();
|
||||||
Log.w(TAG, logTag(session)+response+" received error: "+error);
|
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" received error: "+error);
|
||||||
if(error.has("details")){
|
if(error.has("details")){
|
||||||
MastodonDetailedErrorResponse err=new MastodonDetailedErrorResponse(error.get("error").getAsString(), response.code(), null);
|
MastodonDetailedErrorResponse err=new MastodonDetailedErrorResponse(error.get("error").getAsString(), response.code(), null);
|
||||||
HashMap<String, List<MastodonDetailedErrorResponse.FieldError>> details=new HashMap<>();
|
HashMap<String, List<MastodonDetailedErrorResponse.FieldError>> details=new HashMap<>();
|
||||||
@@ -196,7 +196,7 @@ public class MastodonAPIController{
|
|||||||
});
|
});
|
||||||
}catch(Exception x){
|
}catch(Exception x){
|
||||||
if(BuildConfig.DEBUG)
|
if(BuildConfig.DEBUG)
|
||||||
Log.w(TAG, logTag(session)+"error creating and sending http request", x);
|
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] error creating and sending http request", x);
|
||||||
req.onError(x.getLocalizedMessage(), 0, x);
|
req.onError(x.getLocalizedMessage(), 0, x);
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
@@ -209,8 +209,4 @@ public class MastodonAPIController{
|
|||||||
public static OkHttpClient getHttpClient(){
|
public static OkHttpClient getHttpClient(){
|
||||||
return httpClient;
|
return httpClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String logTag(AccountSession session){
|
|
||||||
return "["+(session==null ? "no-auth" : session.getID())+"] ";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,8 +154,6 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public RequestBody getRequestBody() throws IOException{
|
public RequestBody getRequestBody() throws IOException{
|
||||||
if(requestBody instanceof RequestBody rb)
|
|
||||||
return rb;
|
|
||||||
return requestBody==null ? null : new JsonObjectRequestBody(requestBody);
|
return requestBody==null ? null : new JsonObjectRequestBody(requestBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.accounts;
|
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.model.FollowList;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class GetAccountLists extends MastodonAPIRequest<List<FollowList>>{
|
|
||||||
public GetAccountLists(String id){
|
|
||||||
super(HttpMethod.GET, "/accounts/"+id+"/lists", new TypeToken<>(){});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.accounts;
|
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.model.Account;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class SearchAccounts extends MastodonAPIRequest<List<Account>>{
|
|
||||||
public SearchAccounts(String q, int limit, int offset, boolean resolve, boolean following){
|
|
||||||
super(HttpMethod.GET, "/accounts/search", new TypeToken<>(){});
|
|
||||||
addQueryParameter("q", q);
|
|
||||||
if(limit>0)
|
|
||||||
addQueryParameter("limit", limit+"");
|
|
||||||
if(offset>0)
|
|
||||||
addQueryParameter("offset", offset+"");
|
|
||||||
if(resolve)
|
|
||||||
addQueryParameter("resolve", "true");
|
|
||||||
if(following)
|
|
||||||
addQueryParameter("following", "true");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,9 +2,6 @@ package org.joinmastodon.android.api.requests.filters;
|
|||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
import androidx.annotation.Keep;
|
|
||||||
|
|
||||||
@Keep
|
|
||||||
class KeywordAttribute{
|
class KeywordAttribute{
|
||||||
public String id;
|
public String id;
|
||||||
@SerializedName("_destroy")
|
@SerializedName("_destroy")
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.lists;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.ResultlessMastodonAPIRequest;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import okhttp3.FormBody;
|
|
||||||
|
|
||||||
public class AddAccountsToList extends ResultlessMastodonAPIRequest{
|
|
||||||
public AddAccountsToList(String listID, Collection<String> accountIDs){
|
|
||||||
super(HttpMethod.POST, "/lists/"+listID+"/accounts");
|
|
||||||
FormBody.Builder builder=new FormBody.Builder(StandardCharsets.UTF_8);
|
|
||||||
for(String id:accountIDs){
|
|
||||||
builder.add("account_ids[]", id);
|
|
||||||
}
|
|
||||||
setRequestBody(builder.build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.lists;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.model.FollowList;
|
|
||||||
|
|
||||||
public class CreateList extends MastodonAPIRequest<FollowList>{
|
|
||||||
public CreateList(String title, FollowList.RepliesPolicy repliesPolicy, boolean exclusive){
|
|
||||||
super(HttpMethod.POST, "/lists", FollowList.class);
|
|
||||||
setRequestBody(new Request(title, repliesPolicy, exclusive));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Request{
|
|
||||||
public String title;
|
|
||||||
public FollowList.RepliesPolicy repliesPolicy;
|
|
||||||
public boolean exclusive;
|
|
||||||
|
|
||||||
public Request(String title, FollowList.RepliesPolicy repliesPolicy, boolean exclusive){
|
|
||||||
this.title=title;
|
|
||||||
this.repliesPolicy=repliesPolicy;
|
|
||||||
this.exclusive=exclusive;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.lists;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.ResultlessMastodonAPIRequest;
|
|
||||||
|
|
||||||
public class DeleteList extends ResultlessMastodonAPIRequest{
|
|
||||||
public DeleteList(String id){
|
|
||||||
super(HttpMethod.DELETE, "/lists/"+id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.lists;
|
|
||||||
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
|
||||||
import org.joinmastodon.android.model.Account;
|
|
||||||
|
|
||||||
public class GetListAccounts extends HeaderPaginationRequest<Account>{
|
|
||||||
public GetListAccounts(String listID, String maxID, int limit){
|
|
||||||
super(HttpMethod.GET, "/lists/"+listID+"/accounts", new TypeToken<>(){});
|
|
||||||
if(!TextUtils.isEmpty(maxID))
|
|
||||||
addQueryParameter("max_id", maxID);
|
|
||||||
addQueryParameter("limit", String.valueOf(limit));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.lists;
|
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.model.FollowList;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class GetLists extends MastodonAPIRequest<List<FollowList>>{
|
|
||||||
public GetLists(){
|
|
||||||
super(HttpMethod.GET, "/lists", new TypeToken<>(){});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.lists;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.ResultlessMastodonAPIRequest;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import okhttp3.FormBody;
|
|
||||||
|
|
||||||
public class RemoveAccountsFromList extends ResultlessMastodonAPIRequest{
|
|
||||||
public RemoveAccountsFromList(String listID, Collection<String> accountIDs){
|
|
||||||
super(HttpMethod.DELETE, "/lists/"+listID+"/accounts");
|
|
||||||
FormBody.Builder builder=new FormBody.Builder(StandardCharsets.UTF_8);
|
|
||||||
for(String id:accountIDs){
|
|
||||||
builder.add("account_ids[]", id);
|
|
||||||
}
|
|
||||||
setRequestBody(builder.build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.lists;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.model.FollowList;
|
|
||||||
|
|
||||||
public class UpdateList extends MastodonAPIRequest<FollowList>{
|
|
||||||
public UpdateList(String listID, String title, FollowList.RepliesPolicy repliesPolicy, boolean exclusive){
|
|
||||||
super(HttpMethod.PUT, "/lists/"+listID, FollowList.class);
|
|
||||||
setRequestBody(new Request(title, repliesPolicy, exclusive));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Request{
|
|
||||||
public String title;
|
|
||||||
public FollowList.RepliesPolicy repliesPolicy;
|
|
||||||
public boolean exclusive;
|
|
||||||
|
|
||||||
public Request(String title, FollowList.RepliesPolicy repliesPolicy, boolean exclusive){
|
|
||||||
this.title=title;
|
|
||||||
this.repliesPolicy=repliesPolicy;
|
|
||||||
this.exclusive=exclusive;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.tags;
|
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
|
||||||
|
|
||||||
public class GetFollowedTags extends HeaderPaginationRequest<Hashtag>{
|
|
||||||
public GetFollowedTags(String maxID, int limit){
|
|
||||||
super(HttpMethod.GET, "/followed_tags", new TypeToken<>(){});
|
|
||||||
if(maxID!=null)
|
|
||||||
addQueryParameter("max_id", maxID);
|
|
||||||
if(limit>0)
|
|
||||||
addQueryParameter("limit", limit+"");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.timelines;
|
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.model.Status;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class GetListTimeline extends MastodonAPIRequest<List<Status>>{
|
|
||||||
public GetListTimeline(String listID, String maxID, String minID, int limit, String sinceID){
|
|
||||||
super(HttpMethod.GET, "/timelines/list/"+listID, new TypeToken<>(){});
|
|
||||||
if(maxID!=null)
|
|
||||||
addQueryParameter("max_id", maxID);
|
|
||||||
if(minID!=null)
|
|
||||||
addQueryParameter("min_id", minID);
|
|
||||||
if(limit>0)
|
|
||||||
addQueryParameter("limit", ""+limit);
|
|
||||||
if(sinceID!=null)
|
|
||||||
addQueryParameter("since_id", sinceID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,7 @@ import org.joinmastodon.android.model.Status;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class GetPublicTimeline extends MastodonAPIRequest<List<Status>>{
|
public class GetPublicTimeline extends MastodonAPIRequest<List<Status>>{
|
||||||
public GetPublicTimeline(boolean local, boolean remote, String maxID, String minID, int limit, String sinceID){
|
public GetPublicTimeline(boolean local, boolean remote, String maxID, int limit){
|
||||||
super(HttpMethod.GET, "/timelines/public", new TypeToken<>(){});
|
super(HttpMethod.GET, "/timelines/public", new TypeToken<>(){});
|
||||||
if(local)
|
if(local)
|
||||||
addQueryParameter("local", "true");
|
addQueryParameter("local", "true");
|
||||||
@@ -18,10 +18,6 @@ public class GetPublicTimeline extends MastodonAPIRequest<List<Status>>{
|
|||||||
addQueryParameter("remote", "true");
|
addQueryParameter("remote", "true");
|
||||||
if(!TextUtils.isEmpty(maxID))
|
if(!TextUtils.isEmpty(maxID))
|
||||||
addQueryParameter("max_id", maxID);
|
addQueryParameter("max_id", maxID);
|
||||||
if(!TextUtils.isEmpty(minID))
|
|
||||||
addQueryParameter("min_id", minID);
|
|
||||||
if(!TextUtils.isEmpty(sinceID))
|
|
||||||
addQueryParameter("since_id", sinceID);
|
|
||||||
if(limit>0)
|
if(limit>0)
|
||||||
addQueryParameter("limit", limit+"");
|
addQueryParameter("limit", limit+"");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import org.joinmastodon.android.model.Application;
|
|||||||
import org.joinmastodon.android.model.FilterAction;
|
import org.joinmastodon.android.model.FilterAction;
|
||||||
import org.joinmastodon.android.model.FilterContext;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.FilterResult;
|
import org.joinmastodon.android.model.FilterResult;
|
||||||
import org.joinmastodon.android.model.FollowList;
|
|
||||||
import org.joinmastodon.android.model.LegacyFilter;
|
import org.joinmastodon.android.model.LegacyFilter;
|
||||||
import org.joinmastodon.android.model.Preferences;
|
import org.joinmastodon.android.model.Preferences;
|
||||||
import org.joinmastodon.android.model.PushSubscription;
|
import org.joinmastodon.android.model.PushSubscription;
|
||||||
@@ -33,7 +32,6 @@ import org.joinmastodon.android.model.TimelineMarkers;
|
|||||||
import org.joinmastodon.android.model.Token;
|
import org.joinmastodon.android.model.Token;
|
||||||
import org.joinmastodon.android.utils.ObjectIdComparator;
|
import org.joinmastodon.android.utils.ObjectIdComparator;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@@ -68,7 +66,6 @@ public class AccountSession{
|
|||||||
private transient SharedPreferences prefs;
|
private transient SharedPreferences prefs;
|
||||||
private transient boolean preferencesNeedSaving;
|
private transient boolean preferencesNeedSaving;
|
||||||
private transient AccountLocalPreferences localPreferences;
|
private transient AccountLocalPreferences localPreferences;
|
||||||
private transient List<FollowList> lists;
|
|
||||||
|
|
||||||
AccountSession(Token token, Account self, Application app, String domain, boolean activated, AccountActivationInfo activationInfo){
|
AccountSession(Token token, Account self, Application app, String domain, boolean activated, AccountActivationInfo activationInfo){
|
||||||
this.token=token;
|
this.token=token;
|
||||||
@@ -268,12 +265,4 @@ public class AccountSession{
|
|||||||
public void updateAccountInfo(){
|
public void updateAccountInfo(){
|
||||||
AccountSessionManager.getInstance().updateSessionLocalInfo(this);
|
AccountSessionManager.getInstance().updateSessionLocalInfo(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isNotificationsMentionsOnly(){
|
|
||||||
return getRawLocalPreferences().getBoolean("notificationsMentionsOnly", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNotificationsMentionsOnly(boolean mentionsOnly){
|
|
||||||
getRawLocalPreferences().edit().putBoolean("notificationsMentionsOnly", mentionsOnly).apply();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,6 @@ public class AccountSessionManager{
|
|||||||
public void removeAccount(String id){
|
public void removeAccount(String id){
|
||||||
AccountSession session=getAccount(id);
|
AccountSession session=getAccount(id);
|
||||||
session.getCacheController().closeDatabase();
|
session.getCacheController().closeDatabase();
|
||||||
session.getCacheController().getListsFile().delete();
|
|
||||||
MastodonApp.context.deleteDatabase(id+".db");
|
MastodonApp.context.deleteDatabase(id+".db");
|
||||||
MastodonApp.context.getSharedPreferences(id, 0).edit().clear().commit();
|
MastodonApp.context.getSharedPreferences(id, 0).edit().clear().commit();
|
||||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
package org.joinmastodon.android.events;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.model.Account;
|
|
||||||
|
|
||||||
public class AccountAddedToListEvent{
|
|
||||||
public final String accountID;
|
|
||||||
public final String listID;
|
|
||||||
public final Account account;
|
|
||||||
|
|
||||||
public AccountAddedToListEvent(String accountID, String listID, Account account){
|
|
||||||
this.accountID=accountID;
|
|
||||||
this.listID=listID;
|
|
||||||
this.account=account;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package org.joinmastodon.android.events;
|
|
||||||
|
|
||||||
public class AccountRemovedFromListEvent{
|
|
||||||
public final String accountID;
|
|
||||||
public final String listID;
|
|
||||||
public final String targetAccountID;
|
|
||||||
|
|
||||||
public AccountRemovedFromListEvent(String accountID, String listID, String targetAccountID){
|
|
||||||
this.accountID=accountID;
|
|
||||||
this.listID=listID;
|
|
||||||
this.targetAccountID=targetAccountID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package org.joinmastodon.android.events;
|
|
||||||
|
|
||||||
public class FinishListCreationFragmentEvent{
|
|
||||||
public final String accountID;
|
|
||||||
public final String listID;
|
|
||||||
|
|
||||||
public FinishListCreationFragmentEvent(String accountID, String listID){
|
|
||||||
this.accountID=accountID;
|
|
||||||
this.listID=listID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package org.joinmastodon.android.events;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.model.FollowList;
|
|
||||||
|
|
||||||
public class ListCreatedEvent{
|
|
||||||
public final String accountID;
|
|
||||||
public final FollowList list;
|
|
||||||
|
|
||||||
public ListCreatedEvent(String accountID, FollowList list){
|
|
||||||
this.accountID=accountID;
|
|
||||||
this.list=list;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package org.joinmastodon.android.events;
|
|
||||||
|
|
||||||
public class ListDeletedEvent{
|
|
||||||
public final String accountID;
|
|
||||||
public final String listID;
|
|
||||||
|
|
||||||
public ListDeletedEvent(String accountID, String listID){
|
|
||||||
this.accountID=accountID;
|
|
||||||
this.listID=listID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package org.joinmastodon.android.events;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.model.FollowList;
|
|
||||||
|
|
||||||
public class ListUpdatedEvent{
|
|
||||||
public final String accountID;
|
|
||||||
public final FollowList list;
|
|
||||||
|
|
||||||
public ListUpdatedEvent(String accountID, FollowList list){
|
|
||||||
this.accountID=accountID;
|
|
||||||
this.list=list;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.ResultlessMastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountLists;
|
|
||||||
import org.joinmastodon.android.api.requests.lists.AddAccountsToList;
|
|
||||||
import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
|
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
|
||||||
import org.joinmastodon.android.events.AccountAddedToListEvent;
|
|
||||||
import org.joinmastodon.android.events.AccountRemovedFromListEvent;
|
|
||||||
import org.joinmastodon.android.fragments.settings.BaseSettingsFragment;
|
|
||||||
import org.joinmastodon.android.model.Account;
|
|
||||||
import org.joinmastodon.android.model.FollowList;
|
|
||||||
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
import org.parceler.Parcels;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import me.grishka.appkit.api.Callback;
|
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
|
||||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
|
||||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
|
||||||
import me.grishka.appkit.utils.V;
|
|
||||||
|
|
||||||
public class AddAccountToListsFragment extends BaseSettingsFragment<FollowList>{
|
|
||||||
private Account account;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState){
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setTitle(R.string.add_user_to_list_title);
|
|
||||||
account=Parcels.unwrap(getArguments().getParcelable("targetAccount"));
|
|
||||||
loadData();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doLoadData(int offset, int count){
|
|
||||||
AccountSessionManager.get(accountID).getCacheController().getLists(new SimpleCallback<>(this){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(List<FollowList> allLists){
|
|
||||||
if(getActivity()==null)
|
|
||||||
return;
|
|
||||||
loadAccountLists(allLists);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadAccountLists(final List<FollowList> allLists){
|
|
||||||
currentRequest=new GetAccountLists(account.id)
|
|
||||||
.setCallback(new SimpleCallback<>(this){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(List<FollowList> result){
|
|
||||||
Set<String> lists=result.stream().map(l->l.id).collect(Collectors.toSet());
|
|
||||||
onDataLoaded(allLists.stream()
|
|
||||||
.map(l->new CheckableListItem<>(l.title, null, CheckableListItem.Style.CHECKBOX, lists.contains(l.id),
|
|
||||||
R.drawable.ic_list_alt_24px, AddAccountToListsFragment.this::onItemClick, l))
|
|
||||||
.collect(Collectors.toList()), false);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec(accountID);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int indexOfItemsAdapter(){
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected RecyclerView.Adapter<?> getAdapter(){
|
|
||||||
TextView topText=new TextView(getActivity());
|
|
||||||
topText.setTextAppearance(R.style.m3_body_medium);
|
|
||||||
topText.setTextColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurface));
|
|
||||||
topText.setPadding(V.dp(16), V.dp(8), V.dp(16), V.dp(8));
|
|
||||||
topText.setText(getString(R.string.manage_user_lists, account.getDisplayUsername()));
|
|
||||||
|
|
||||||
MergeRecyclerAdapter mergeAdapter=new MergeRecyclerAdapter();
|
|
||||||
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(topText));
|
|
||||||
mergeAdapter.addAdapter(super.getAdapter());
|
|
||||||
return mergeAdapter;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onItemClick(CheckableListItem<FollowList> item){
|
|
||||||
boolean add=!item.checked;
|
|
||||||
ResultlessMastodonAPIRequest req=add ? new AddAccountsToList(item.parentObject.id, Set.of(account.id)) : new RemoveAccountsFromList(item.parentObject.id, Set.of(account.id));
|
|
||||||
req.setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Void result){
|
|
||||||
item.checked=add;
|
|
||||||
rebindItem(item);
|
|
||||||
if(add){
|
|
||||||
E.post(new AccountAddedToListEvent(accountID, item.parentObject.id, account));
|
|
||||||
}else{
|
|
||||||
E.post(new AccountRemovedFromListEvent(accountID, item.parentObject.id, account.id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
error.showToast(getActivity());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.wrapProgress(getActivity(), R.string.loading, false)
|
|
||||||
.exec(accountID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.Spinner;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.lists.DeleteList;
|
|
||||||
import org.joinmastodon.android.api.requests.lists.GetListAccounts;
|
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
|
||||||
import org.joinmastodon.android.events.ListDeletedEvent;
|
|
||||||
import org.joinmastodon.android.fragments.settings.BaseSettingsFragment;
|
|
||||||
import org.joinmastodon.android.model.Account;
|
|
||||||
import org.joinmastodon.android.model.FollowList;
|
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
|
||||||
import org.joinmastodon.android.model.viewmodel.AvatarPileListItem;
|
|
||||||
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
|
|
||||||
import org.joinmastodon.android.model.viewmodel.ListItem;
|
|
||||||
import org.joinmastodon.android.ui.views.FloatingHintEditTextLayout;
|
|
||||||
import org.parceler.Parcels;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import me.grishka.appkit.Nav;
|
|
||||||
import me.grishka.appkit.api.APIRequest;
|
|
||||||
import me.grishka.appkit.api.Callback;
|
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
|
||||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
|
||||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
|
||||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
|
||||||
import me.grishka.appkit.utils.V;
|
|
||||||
|
|
||||||
public abstract class BaseEditListFragment extends BaseSettingsFragment<Void>{
|
|
||||||
protected FollowList followList;
|
|
||||||
protected AvatarPileListItem<Void> membersItem;
|
|
||||||
protected CheckableListItem<Void> exclusiveItem;
|
|
||||||
protected FloatingHintEditTextLayout titleEditLayout;
|
|
||||||
protected EditText titleEdit;
|
|
||||||
protected Spinner showRepliesSpinner;
|
|
||||||
private APIRequest<?> getMembersRequest;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState){
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
followList=Parcels.unwrap(getArguments().getParcelable("list"));
|
|
||||||
|
|
||||||
membersItem=new AvatarPileListItem<>(getString(R.string.list_members), null, List.of(), 0, i->onMembersClick(), null, false);
|
|
||||||
List<ListItem<Void>> items=new ArrayList<>();
|
|
||||||
if(followList!=null){
|
|
||||||
items.add(membersItem);
|
|
||||||
}
|
|
||||||
exclusiveItem=new CheckableListItem<>(R.string.list_exclusive, R.string.list_exclusive_subtitle, CheckableListItem.Style.SWITCH, followList!=null && followList.exclusive, this::toggleCheckableItem);
|
|
||||||
items.add(exclusiveItem);
|
|
||||||
onDataLoaded(items);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy(){
|
|
||||||
super.onDestroy();
|
|
||||||
if(getMembersRequest!=null)
|
|
||||||
getMembersRequest.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doLoadData(int offset, int count){}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected RecyclerView.Adapter<?> getAdapter(){
|
|
||||||
LinearLayout topView=new LinearLayout(getActivity());
|
|
||||||
topView.setOrientation(LinearLayout.VERTICAL);
|
|
||||||
|
|
||||||
titleEditLayout=(FloatingHintEditTextLayout) getActivity().getLayoutInflater().inflate(R.layout.floating_hint_edit_text, topView, false);
|
|
||||||
titleEdit=titleEditLayout.findViewById(R.id.edit);
|
|
||||||
titleEdit.setHint(R.string.list_name);
|
|
||||||
titleEditLayout.updateHint();
|
|
||||||
if(followList!=null)
|
|
||||||
titleEdit.setText(followList.title);
|
|
||||||
topView.addView(titleEditLayout);
|
|
||||||
|
|
||||||
FloatingHintEditTextLayout showRepliesLayout=(FloatingHintEditTextLayout) getActivity().getLayoutInflater().inflate(R.layout.floating_hint_spinner, topView, false);
|
|
||||||
showRepliesSpinner=showRepliesLayout.findViewById(R.id.spinner);
|
|
||||||
showRepliesLayout.setHint(R.string.list_show_replies_to);
|
|
||||||
topView.addView(showRepliesLayout);
|
|
||||||
ArrayAdapter<String> spinnerAdapter=new ArrayAdapter<>(getActivity(), R.layout.item_spinner, List.of(
|
|
||||||
getString(R.string.list_replies_no_one),
|
|
||||||
getString(R.string.list_replies_members),
|
|
||||||
getString(R.string.list_replies_anyone)
|
|
||||||
));
|
|
||||||
showRepliesSpinner.setAdapter(spinnerAdapter);
|
|
||||||
showRepliesSpinner.setSelection(switch(followList!=null ? followList.repliesPolicy : FollowList.RepliesPolicy.LIST){
|
|
||||||
case FOLLOWED -> 2;
|
|
||||||
case LIST -> 1;
|
|
||||||
case NONE -> 0;
|
|
||||||
});
|
|
||||||
ViewGroup.MarginLayoutParams llp=(ViewGroup.MarginLayoutParams)showRepliesLayout.getLabel().getLayoutParams();
|
|
||||||
llp.setMarginStart(llp.getMarginStart()+V.dp(16));
|
|
||||||
|
|
||||||
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
|
|
||||||
adapter.addAdapter(new SingleViewRecyclerAdapter(topView));
|
|
||||||
adapter.addAdapter(super.getAdapter());
|
|
||||||
return adapter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int indexOfItemsAdapter(){
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void doDeleteList(){
|
|
||||||
new DeleteList(followList.id)
|
|
||||||
.setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Void result){
|
|
||||||
AccountSessionManager.get(accountID).getCacheController().deleteList(followList.id);
|
|
||||||
E.post(new ListDeletedEvent(accountID, followList.id));
|
|
||||||
Nav.finish(BaseEditListFragment.this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
Activity activity=getActivity();
|
|
||||||
if(activity==null)
|
|
||||||
return;
|
|
||||||
error.showToast(activity);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.wrapProgress(getActivity(), R.string.loading, true)
|
|
||||||
.exec(accountID);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onMembersClick(){
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
args.putParcelable("list", Parcels.wrap(followList));
|
|
||||||
Nav.go(getActivity(), ListMembersFragment.class, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void loadMembers(){
|
|
||||||
getMembersRequest=new GetListAccounts(followList.id, null, 3)
|
|
||||||
.setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(HeaderPaginationList<Account> result){
|
|
||||||
getMembersRequest=null;
|
|
||||||
membersItem.avatars=new ArrayList<>();
|
|
||||||
for(int i=0;i<Math.min(3, result.size());i++){
|
|
||||||
Account acc=result.get(i);
|
|
||||||
membersItem.avatars.add(new UrlImageLoaderRequest(acc.avatarStatic, V.dp(32), V.dp(32)));
|
|
||||||
}
|
|
||||||
rebindItem(membersItem);
|
|
||||||
imgLoader.updateImages();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
getMembersRequest=null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec(accountID);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected FollowList.RepliesPolicy getSelectedRepliesPolicy(){
|
|
||||||
return switch(showRepliesSpinner.getSelectedItemPosition()){
|
|
||||||
case 0 -> FollowList.RepliesPolicy.NONE;
|
|
||||||
case 1 -> FollowList.RepliesPolicy.LIST;
|
|
||||||
case 2 -> FollowList.RepliesPolicy.FOLLOWED;
|
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+showRepliesSpinner.getSelectedItemPosition());
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,14 +19,17 @@ import android.text.TextUtils;
|
|||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.text.style.BackgroundColorSpan;
|
import android.text.style.BackgroundColorSpan;
|
||||||
import android.text.style.ForegroundColorSpan;
|
import android.text.style.ForegroundColorSpan;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
import android.view.SoundEffectConstants;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewOutlineProvider;
|
import android.view.ViewOutlineProvider;
|
||||||
|
import android.view.ViewTreeObserver;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.view.inputmethod.InputConnection;
|
import android.view.inputmethod.InputConnection;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
@@ -46,6 +49,7 @@ import org.joinmastodon.android.E;
|
|||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.MastodonErrorResponse;
|
import org.joinmastodon.android.api.MastodonErrorResponse;
|
||||||
|
import org.joinmastodon.android.api.requests.accounts.GetPreferences;
|
||||||
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
|
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
|
||||||
import org.joinmastodon.android.api.requests.statuses.EditStatus;
|
import org.joinmastodon.android.api.requests.statuses.EditStatus;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
@@ -53,7 +57,7 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
|
|||||||
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.StatusUpdatedEvent;
|
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
||||||
import org.joinmastodon.android.fragments.account_list.AccountSearchFragment;
|
import org.joinmastodon.android.fragments.account_list.ComposeAccountSearchFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Emoji;
|
import org.joinmastodon.android.model.Emoji;
|
||||||
import org.joinmastodon.android.model.EmojiCategory;
|
import org.joinmastodon.android.model.EmojiCategory;
|
||||||
@@ -336,7 +340,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
public void onLaunchAccountSearch(){
|
public void onLaunchAccountSearch(){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
Nav.goForResult(getActivity(), AccountSearchFragment.class, args, AUTOCOMPLETE_ACCOUNT_RESULT, ComposeFragment.this);
|
Nav.goForResult(getActivity(), ComposeAccountSearchFragment.class, args, AUTOCOMPLETE_ACCOUNT_RESULT, ComposeFragment.this);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
View autocompleteView=autocompleteViewController.getView();
|
View autocompleteView=autocompleteViewController.getView();
|
||||||
@@ -1012,26 +1016,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
return new String[]{"image/jpeg", "image/gif", "image/png", "video/mp4"};
|
return new String[]{"image/jpeg", "image/gif", "image/png", "video/mp4"};
|
||||||
}
|
}
|
||||||
|
|
||||||
private String sanitizeMediaDescription(String description){
|
|
||||||
if(description == null){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The Gboard android keyboard attaches this text whenever the user
|
|
||||||
// pastes something from the keyboard's suggestion bar.
|
|
||||||
// Due to different end user locales, the exact text may vary, but at
|
|
||||||
// least in version 13.4.08, all of the translations contained the
|
|
||||||
// string "Gboard".
|
|
||||||
if (description.contains("Gboard")){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onAddMediaAttachmentFromEditText(Uri uri, String description){
|
public boolean onAddMediaAttachmentFromEditText(Uri uri, String description){
|
||||||
description = sanitizeMediaDescription(description);
|
|
||||||
return mediaViewController.addMediaAttachment(uri, description);
|
return mediaViewController.addMediaAttachment(uri, description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,323 +0,0 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
|
||||||
|
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.Gravity;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.ViewStub;
|
|
||||||
import android.view.WindowInsets;
|
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.lists.AddAccountsToList;
|
|
||||||
import org.joinmastodon.android.api.requests.lists.GetListAccounts;
|
|
||||||
import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
|
|
||||||
import org.joinmastodon.android.events.FinishListCreationFragmentEvent;
|
|
||||||
import org.joinmastodon.android.fragments.account_list.AddNewListMembersFragment;
|
|
||||||
import org.joinmastodon.android.fragments.account_list.BaseAccountListFragment;
|
|
||||||
import org.joinmastodon.android.model.Account;
|
|
||||||
import org.joinmastodon.android.model.FollowList;
|
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
|
||||||
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
|
|
||||||
import org.joinmastodon.android.ui.views.CurlyArrowEmptyView;
|
|
||||||
import org.parceler.Parcels;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
|
||||||
import me.grishka.appkit.api.Callback;
|
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
|
||||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
|
||||||
import me.grishka.appkit.fragments.WindowInsetsAwareFragment;
|
|
||||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
|
||||||
import me.grishka.appkit.utils.V;
|
|
||||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
|
||||||
|
|
||||||
public class CreateListAddMembersFragment extends BaseAccountListFragment implements OnBackPressedListener, AddNewListMembersFragment.Listener{
|
|
||||||
private FollowList followList;
|
|
||||||
private Button nextButton;
|
|
||||||
private View buttonBar;
|
|
||||||
private FragmentRootLinearLayout rootView;
|
|
||||||
private FrameLayout searchFragmentContainer;
|
|
||||||
private FrameLayout fragmentContentWrap;
|
|
||||||
private AddNewListMembersFragment searchFragment;
|
|
||||||
private WindowInsets lastInsets;
|
|
||||||
private boolean dismissingSearchFragment;
|
|
||||||
private HashSet<String> accountIDsInList=new HashSet<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState){
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setTitle(R.string.manage_list_members);
|
|
||||||
setSubtitle(getString(R.string.step_x_of_y, 2, 2));
|
|
||||||
setLayout(R.layout.fragment_login);
|
|
||||||
setEmptyText(R.string.list_no_members);
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
|
|
||||||
followList=Parcels.unwrap(getArguments().getParcelable("list"));
|
|
||||||
if(savedInstanceState!=null || getArguments().getBoolean("needLoadMembers", false)){
|
|
||||||
loadData();
|
|
||||||
}else{
|
|
||||||
onDataLoaded(List.of());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doLoadData(int offset, int count){
|
|
||||||
currentRequest=new GetListAccounts(followList.id, null, 0)
|
|
||||||
.setCallback(new SimpleCallback<>(this){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(HeaderPaginationList<Account> result){
|
|
||||||
for(Account acc:result)
|
|
||||||
accountIDsInList.add(acc.id);
|
|
||||||
onDataLoaded(result.stream().map(a->new AccountViewModel(a, accountID)).collect(Collectors.toList()));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec(accountID);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
|
||||||
View view=super.onCreateView(inflater, container, savedInstanceState);
|
|
||||||
FrameLayout wrapper=new FrameLayout(getActivity());
|
|
||||||
wrapper.addView(view);
|
|
||||||
rootView=(FragmentRootLinearLayout) view;
|
|
||||||
fragmentContentWrap=wrapper;
|
|
||||||
return wrapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
|
||||||
nextButton=view.findViewById(R.id.btn_next);
|
|
||||||
nextButton.setOnClickListener(this::onNextClick);
|
|
||||||
nextButton.setText(R.string.done);
|
|
||||||
buttonBar=view.findViewById(R.id.button_bar);
|
|
||||||
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onApplyWindowInsets(WindowInsets insets){
|
|
||||||
lastInsets=insets;
|
|
||||||
if(searchFragment!=null)
|
|
||||||
searchFragment.onApplyWindowInsets(insets);
|
|
||||||
insets=UiUtils.applyBottomInsetToFixedView(buttonBar, insets);
|
|
||||||
rootView.dispatchApplyWindowInsets(insets);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<View> getViewsForElevationEffect(){
|
|
||||||
return List.of(getToolbar(), buttonBar);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
|
||||||
MenuItem item=menu.add(R.string.add_list_member);
|
|
||||||
item.setIcon(R.drawable.ic_add_24px);
|
|
||||||
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item){
|
|
||||||
if(searchFragmentContainer!=null)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
searchFragmentContainer=new FrameLayout(getActivity());
|
|
||||||
searchFragmentContainer.setId(R.id.search_fragment);
|
|
||||||
fragmentContentWrap.addView(searchFragmentContainer);
|
|
||||||
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
args.putParcelable("list", Parcels.wrap(followList));
|
|
||||||
args.putBoolean("_can_go_back", true);
|
|
||||||
searchFragment=new AddNewListMembersFragment(this);
|
|
||||||
searchFragment.setArguments(args);
|
|
||||||
getChildFragmentManager().beginTransaction().add(R.id.search_fragment, searchFragment).commit();
|
|
||||||
getChildFragmentManager().executePendingTransactions();
|
|
||||||
if(lastInsets!=null)
|
|
||||||
searchFragment.onApplyWindowInsets(lastInsets);
|
|
||||||
searchFragmentContainer.setTranslationX(V.dp(100));
|
|
||||||
searchFragmentContainer.setAlpha(0f);
|
|
||||||
searchFragmentContainer.animate().translationX(0).alpha(1).setDuration(300).withLayer().setInterpolator(CubicBezierInterpolator.DEFAULT).withEndAction(()->{
|
|
||||||
rootView.setVisibility(View.GONE);
|
|
||||||
}).start();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void initializeEmptyView(View contentView){
|
|
||||||
ViewStub emptyStub=contentView.findViewById(R.id.empty);
|
|
||||||
emptyStub.setLayoutResource(R.layout.empty_with_arrow);
|
|
||||||
super.initializeEmptyView(contentView);
|
|
||||||
TextView emptySecondary=contentView.findViewById(R.id.empty_text_secondary);
|
|
||||||
emptySecondary.setText(R.string.list_find_users);
|
|
||||||
CurlyArrowEmptyView arrowView=(CurlyArrowEmptyView) emptyView;
|
|
||||||
arrowView.setGravityAndOffsets(Gravity.TOP | Gravity.END, 24, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setStatusBarColor(int color){
|
|
||||||
rootView.setStatusBarColor(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setNavigationBarColor(int color){
|
|
||||||
rootView.setNavigationBarColor(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void dismissSearchFragment(){
|
|
||||||
if(searchFragment==null || dismissingSearchFragment)
|
|
||||||
return;
|
|
||||||
dismissingSearchFragment=true;
|
|
||||||
rootView.setVisibility(View.VISIBLE);
|
|
||||||
searchFragmentContainer.animate().translationX(V.dp(100)).alpha(0).setDuration(200).withLayer().setInterpolator(CubicBezierInterpolator.DEFAULT).withEndAction(()->{
|
|
||||||
getChildFragmentManager().beginTransaction().remove(searchFragment).commit();
|
|
||||||
getChildFragmentManager().executePendingTransactions();
|
|
||||||
fragmentContentWrap.removeView(searchFragmentContainer);
|
|
||||||
searchFragmentContainer=null;
|
|
||||||
searchFragment=null;
|
|
||||||
dismissingSearchFragment=false;
|
|
||||||
}).start();
|
|
||||||
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onNextClick(View v){
|
|
||||||
E.post(new FinishListCreationFragmentEvent(accountID, followList.id));
|
|
||||||
Nav.finish(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onBackPressed(){
|
|
||||||
if(searchFragment!=null){
|
|
||||||
dismissSearchFragment();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAccountInList(AccountViewModel account){
|
|
||||||
return accountIDsInList.contains(account.account.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addAccountToList(AccountViewModel account, Runnable onDone){
|
|
||||||
new AddAccountsToList(followList.id, Set.of(account.account.id))
|
|
||||||
.setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Void result){
|
|
||||||
accountIDsInList.add(account.account.id);
|
|
||||||
if(onDone!=null)
|
|
||||||
onDone.run();
|
|
||||||
int i=0;
|
|
||||||
for(AccountViewModel acc:data){
|
|
||||||
if(acc.account.id.equals(account.account.id)){
|
|
||||||
list.getAdapter().notifyItemChanged(i);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
int pos=data.size();
|
|
||||||
data.add(account);
|
|
||||||
list.getAdapter().notifyItemInserted(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
error.showToast(getActivity());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec(accountID);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeAccountAccountFromList(AccountViewModel account, Runnable onDone){
|
|
||||||
new RemoveAccountsFromList(followList.id, Set.of(account.account.id))
|
|
||||||
.setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Void result){
|
|
||||||
accountIDsInList.remove(account.account.id);
|
|
||||||
if(onDone!=null)
|
|
||||||
onDone.run();
|
|
||||||
int i=0;
|
|
||||||
for(AccountViewModel acc:data){
|
|
||||||
if(acc.account.id.equals(account.account.id)){
|
|
||||||
list.getAdapter().notifyItemChanged(i);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
error.showToast(getActivity());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec(accountID);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onConfigureViewHolder(AccountViewHolder holder){
|
|
||||||
holder.setStyle(AccountViewHolder.AccessoryType.CUSTOM_BUTTON, false);
|
|
||||||
holder.setOnLongClickListener(vh->false);
|
|
||||||
Button button=holder.getButton();
|
|
||||||
button.setPadding(V.dp(24), 0, V.dp(24), 0);
|
|
||||||
button.setMinimumWidth(0);
|
|
||||||
button.setMinWidth(0);
|
|
||||||
button.setOnClickListener(v->{
|
|
||||||
holder.setActionProgressVisible(true);
|
|
||||||
holder.itemView.setHasTransientState(true);
|
|
||||||
Runnable onDone=()->{
|
|
||||||
holder.setActionProgressVisible(false);
|
|
||||||
holder.itemView.setHasTransientState(false);
|
|
||||||
};
|
|
||||||
AccountViewModel account=holder.getItem();
|
|
||||||
if(isAccountInList(account)){
|
|
||||||
removeAccountAccountFromList(account, onDone);
|
|
||||||
}else{
|
|
||||||
addAccountToList(account, onDone);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onBindViewHolder(AccountViewHolder holder){
|
|
||||||
Button button=holder.getButton();
|
|
||||||
int textRes, styleRes;
|
|
||||||
if(isAccountInList(holder.getItem())){
|
|
||||||
textRes=R.string.remove;
|
|
||||||
styleRes=R.style.Widget_Mastodon_M3_Button_Tonal_Error;
|
|
||||||
}else{
|
|
||||||
textRes=R.string.add;
|
|
||||||
styleRes=R.style.Widget_Mastodon_M3_Button_Filled;
|
|
||||||
}
|
|
||||||
button.setText(textRes);
|
|
||||||
TypedArray ta=button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.background});
|
|
||||||
button.setBackground(ta.getDrawable(0));
|
|
||||||
ta.recycle();
|
|
||||||
ta=button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.textColor});
|
|
||||||
button.setTextColor(ta.getColorStateList(0));
|
|
||||||
ta.recycle();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void loadRelationships(List<AccountViewModel> accounts){
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.WindowInsets;
|
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.Button;
|
|
||||||
|
|
||||||
import com.squareup.otto.Subscribe;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.lists.CreateList;
|
|
||||||
import org.joinmastodon.android.api.requests.lists.UpdateList;
|
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
|
||||||
import org.joinmastodon.android.events.FinishListCreationFragmentEvent;
|
|
||||||
import org.joinmastodon.android.events.ListCreatedEvent;
|
|
||||||
import org.joinmastodon.android.events.ListUpdatedEvent;
|
|
||||||
import org.joinmastodon.android.model.FollowList;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
import org.parceler.Parcels;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
|
||||||
import me.grishka.appkit.api.Callback;
|
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
|
||||||
|
|
||||||
public class CreateListFragment extends BaseEditListFragment{
|
|
||||||
private Button nextButton;
|
|
||||||
private View buttonBar;
|
|
||||||
private FollowList followList;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState){
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setTitle(R.string.create_list);
|
|
||||||
setSubtitle(getString(R.string.step_x_of_y, 1, 2));
|
|
||||||
setLayout(R.layout.fragment_login);
|
|
||||||
if(savedInstanceState!=null)
|
|
||||||
followList=Parcels.unwrap(savedInstanceState.getParcelable("list"));
|
|
||||||
E.register(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy(){
|
|
||||||
super.onDestroy();
|
|
||||||
E.unregister(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getNavigationIconDrawableResource(){
|
|
||||||
return R.drawable.ic_baseline_close_24;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean wantsCustomNavigationIcon(){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
|
||||||
nextButton=view.findViewById(R.id.btn_next);
|
|
||||||
nextButton.setOnClickListener(this::onNextClick);
|
|
||||||
nextButton.setText(R.string.create);
|
|
||||||
buttonBar=view.findViewById(R.id.button_bar);
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onApplyWindowInsets(WindowInsets insets){
|
|
||||||
super.onApplyWindowInsets(UiUtils.applyBottomInsetToFixedView(buttonBar, insets));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<View> getViewsForElevationEffect(){
|
|
||||||
return List.of(getToolbar(), buttonBar);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState){
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
outState.putParcelable("list", Parcels.wrap(followList));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onNextClick(View v){
|
|
||||||
String title=titleEdit.getText().toString().trim();
|
|
||||||
if(TextUtils.isEmpty(title)){
|
|
||||||
titleEditLayout.setErrorState(getString(R.string.required_form_field_blank));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(followList==null){
|
|
||||||
new CreateList(title, getSelectedRepliesPolicy(), exclusiveItem.checked)
|
|
||||||
.setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(FollowList result){
|
|
||||||
followList=result;
|
|
||||||
proceed(false);
|
|
||||||
E.post(new ListCreatedEvent(accountID, result));
|
|
||||||
AccountSessionManager.get(accountID).getCacheController().addList(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
error.showToast(getActivity());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.wrapProgress(getActivity(), R.string.loading, true)
|
|
||||||
.exec(accountID);
|
|
||||||
}else if(!title.equals(followList.title) || getSelectedRepliesPolicy()!=followList.repliesPolicy || exclusiveItem.checked!=followList.exclusive){
|
|
||||||
new UpdateList(followList.id, title, getSelectedRepliesPolicy(), exclusiveItem.checked)
|
|
||||||
.setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(FollowList result){
|
|
||||||
followList=result;
|
|
||||||
proceed(true);
|
|
||||||
E.post(new ListUpdatedEvent(accountID, result));
|
|
||||||
AccountSessionManager.get(accountID).getCacheController().updateList(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
error.showToast(getActivity());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.wrapProgress(getActivity(), R.string.loading, true)
|
|
||||||
.exec(accountID);
|
|
||||||
}else{
|
|
||||||
proceed(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void proceed(boolean needLoadMembers){
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
args.putParcelable("list", Parcels.wrap(followList));
|
|
||||||
args.putBoolean("needLoadMembers", needLoadMembers);
|
|
||||||
Nav.go(getActivity(), CreateListAddMembersFragment.class, args);
|
|
||||||
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onFinishListCreationFragment(FinishListCreationFragmentEvent ev){
|
|
||||||
if(ev.accountID.equals(accountID) && followList!=null && ev.listID.equals(followList.id)){
|
|
||||||
Nav.finish(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.lists.UpdateList;
|
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
|
||||||
import org.joinmastodon.android.events.ListUpdatedEvent;
|
|
||||||
import org.joinmastodon.android.model.FollowList;
|
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
|
||||||
|
|
||||||
import me.grishka.appkit.api.Callback;
|
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
|
||||||
|
|
||||||
public class EditListFragment extends BaseEditListFragment{
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState){
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setTitle(R.string.edit_list);
|
|
||||||
loadMembers();
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
|
||||||
menu.add(R.string.delete_list);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item){
|
|
||||||
new M3AlertDialogBuilder(getActivity())
|
|
||||||
.setTitle(R.string.delete_list)
|
|
||||||
.setMessage(getString(R.string.delete_list_confirm, followList.title))
|
|
||||||
.setPositiveButton(R.string.delete, (dlg, which)->doDeleteList())
|
|
||||||
.setNegativeButton(R.string.cancel, null)
|
|
||||||
.show();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy(){
|
|
||||||
super.onDestroy();
|
|
||||||
String newTitle=titleEdit.getText().toString();
|
|
||||||
FollowList.RepliesPolicy newRepliesPolicy=getSelectedRepliesPolicy();
|
|
||||||
boolean newExclusive=exclusiveItem.checked;
|
|
||||||
if(!newTitle.equals(followList.title) || newRepliesPolicy!=followList.repliesPolicy || newExclusive!=followList.exclusive){
|
|
||||||
new UpdateList(followList.id, newTitle, newRepliesPolicy, newExclusive)
|
|
||||||
.setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(FollowList result){
|
|
||||||
AccountSessionManager.get(accountID).getCacheController().updateList(result);
|
|
||||||
E.post(new ListUpdatedEvent(accountID, result));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
// TODO handle errors somehow
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec(accountID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,8 +18,6 @@ import org.joinmastodon.android.api.MastodonErrorResponse;
|
|||||||
import org.joinmastodon.android.api.requests.tags.GetTag;
|
import org.joinmastodon.android.api.requests.tags.GetTag;
|
||||||
import org.joinmastodon.android.api.requests.tags.SetTagFollowed;
|
import org.joinmastodon.android.api.requests.tags.SetTagFollowed;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
|
||||||
import org.joinmastodon.android.model.FilterContext;
|
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.text.SpacerSpan;
|
import org.joinmastodon.android.ui.text.SpacerSpan;
|
||||||
@@ -49,7 +47,6 @@ public class HashtagTimelineFragment extends StatusListFragment{
|
|||||||
private MenuItem followMenuItem;
|
private MenuItem followMenuItem;
|
||||||
private boolean followRequestRunning;
|
private boolean followRequestRunning;
|
||||||
private boolean toolbarContentVisible;
|
private boolean toolbarContentVisible;
|
||||||
private String maxID;
|
|
||||||
|
|
||||||
public HashtagTimelineFragment(){
|
public HashtagTimelineFragment(){
|
||||||
setListLayoutId(R.layout.recycler_fragment_with_fab);
|
setListLayoutId(R.layout.recycler_fragment_with_fab);
|
||||||
@@ -70,13 +67,10 @@ public class HashtagTimelineFragment extends StatusListFragment{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
currentRequest=new GetHashtagTimeline(hashtagName, offset==0 ? null : maxID, null, count)
|
currentRequest=new GetHashtagTimeline(hashtagName, offset==0 ? null : getMaxID(), null, count)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
if(!result.isEmpty())
|
|
||||||
maxID=result.get(result.size()-1).id;
|
|
||||||
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.PUBLIC);
|
|
||||||
onDataLoaded(result, !result.isEmpty());
|
onDataLoaded(result, !result.isEmpty());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -182,7 +176,7 @@ public class HashtagTimelineFragment extends StatusListFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateHeader(){
|
private void updateHeader(){
|
||||||
if(hashtag==null || getActivity()==null)
|
if(hashtag==null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(hashtag.history!=null && !hashtag.history.isEmpty()){
|
if(hashtag.history!=null && !hashtag.history.isEmpty()){
|
||||||
|
|||||||
@@ -150,7 +150,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tabBar.selectTab(currentTab);
|
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,50 +5,40 @@ import android.animation.AnimatorListenerAdapter;
|
|||||||
import android.animation.AnimatorSet;
|
import android.animation.AnimatorSet;
|
||||||
import android.animation.ObjectAnimator;
|
import android.animation.ObjectAnimator;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.accessibility.AccessibilityNodeInfo;
|
|
||||||
import android.view.animation.AnimationUtils;
|
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
import com.squareup.otto.Subscribe;
|
import com.squareup.otto.Subscribe;
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetListTimeline;
|
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||||
|
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||||
import org.joinmastodon.android.fragments.settings.SettingsMainFragment;
|
import org.joinmastodon.android.fragments.settings.SettingsMainFragment;
|
||||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||||
import org.joinmastodon.android.model.FilterContext;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.FollowList;
|
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.TimelineMarkers;
|
import org.joinmastodon.android.model.TimelineMarkers;
|
||||||
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.viewcontrollers.HomeTimelineMenuController;
|
|
||||||
import org.joinmastodon.android.ui.viewcontrollers.ToolbarDropdownMenuController;
|
|
||||||
import org.joinmastodon.android.ui.views.FixedAspectRatioImageView;
|
|
||||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||||
import org.parceler.Parcels;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@@ -62,141 +52,44 @@ import me.grishka.appkit.api.Callback;
|
|||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class HomeTimelineFragment extends StatusListFragment implements ToolbarDropdownMenuController.HostFragment{
|
public class HomeTimelineFragment extends StatusListFragment{
|
||||||
private ImageButton fab;
|
private ImageButton fab;
|
||||||
private LinearLayout listsDropdown;
|
private ImageView toolbarLogo;
|
||||||
private FixedAspectRatioImageView listsDropdownArrow;
|
private Button toolbarShowNewPostsBtn;
|
||||||
private TextView listsDropdownText;
|
|
||||||
private Button newPostsBtn;
|
|
||||||
private View newPostsBtnWrap;
|
|
||||||
private boolean newPostsBtnShown;
|
private boolean newPostsBtnShown;
|
||||||
private AnimatorSet currentNewPostsAnim;
|
private AnimatorSet currentNewPostsAnim;
|
||||||
private ToolbarDropdownMenuController dropdownController;
|
|
||||||
private HomeTimelineMenuController dropdownMainMenuController;
|
|
||||||
private List<FollowList> lists=List.of();
|
|
||||||
private ListMode listMode=ListMode.FOLLOWING;
|
|
||||||
private FollowList currentList;
|
|
||||||
private MergeRecyclerAdapter mergeAdapter;
|
|
||||||
private DiscoverInfoBannerHelper localTimelineBannerHelper;
|
|
||||||
|
|
||||||
private String maxID;
|
private String maxID;
|
||||||
private String lastSavedMarkerID;
|
private String lastSavedMarkerID;
|
||||||
|
|
||||||
public HomeTimelineFragment(){
|
public HomeTimelineFragment(){
|
||||||
setListLayoutId(R.layout.fragment_timeline);
|
setListLayoutId(R.layout.recycler_fragment_with_fab);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState){
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
localTimelineBannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.LOCAL_TIMELINE, accountID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity){
|
public void onAttach(Activity activity){
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
dropdownController=new ToolbarDropdownMenuController(this);
|
|
||||||
dropdownMainMenuController=new HomeTimelineMenuController(dropdownController, new HomeTimelineMenuController.Callback(){
|
|
||||||
@Override
|
|
||||||
public void onFollowingSelected(){
|
|
||||||
if(listMode==ListMode.FOLLOWING)
|
|
||||||
return;
|
|
||||||
listMode=ListMode.FOLLOWING;
|
|
||||||
reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLocalSelected(){
|
|
||||||
if(listMode==ListMode.LOCAL)
|
|
||||||
return;
|
|
||||||
listMode=ListMode.LOCAL;
|
|
||||||
reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<FollowList> getLists(){
|
|
||||||
return lists;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onListSelected(FollowList list){
|
|
||||||
if(listMode==ListMode.LIST && currentList==list)
|
|
||||||
return;
|
|
||||||
listMode=ListMode.LIST;
|
|
||||||
currentList=list;
|
|
||||||
reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
loadData();
|
loadData();
|
||||||
AccountSessionManager.get(accountID).getCacheController().getLists(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(List<FollowList> result){
|
|
||||||
lists=result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
switch(listMode){
|
AccountSessionManager.getInstance()
|
||||||
case FOLLOWING -> {
|
.getAccount(accountID).getCacheController()
|
||||||
AccountSessionManager.getInstance()
|
.getHomeTimeline(offset>0 ? maxID : null, count, refreshing, new SimpleCallback<>(this){
|
||||||
.getAccount(accountID).getCacheController()
|
@Override
|
||||||
.getHomeTimeline(offset>0 ? maxID : null, count, refreshing, new SimpleCallback<>(this){
|
public void onSuccess(CacheablePaginatedResponse<List<Status>> result){
|
||||||
@Override
|
if(getActivity()==null)
|
||||||
public void onSuccess(CacheablePaginatedResponse<List<Status>> result){
|
return;
|
||||||
if(getActivity()==null || listMode!=ListMode.FOLLOWING)
|
onDataLoaded(result.items, !result.items.isEmpty());
|
||||||
return;
|
maxID=result.maxID;
|
||||||
if(refreshing)
|
if(result.isFromCache())
|
||||||
list.scrollToPosition(0);
|
loadNewPosts();
|
||||||
onDataLoaded(result.items, !result.items.isEmpty());
|
}
|
||||||
maxID=result.maxID;
|
});
|
||||||
if(result.isFromCache())
|
|
||||||
loadNewPosts();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
if(listMode!=ListMode.FOLLOWING)
|
|
||||||
return;
|
|
||||||
super.onError(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
case LOCAL -> {
|
|
||||||
currentRequest=new GetPublicTimeline(true, false, offset>0 ? maxID : null, null, count, null)
|
|
||||||
.setCallback(new SimpleCallback<>(this){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(List<Status> result){
|
|
||||||
if(refreshing)
|
|
||||||
list.scrollToPosition(0);
|
|
||||||
maxID=result.isEmpty() ? null : result.get(result.size()-1).id;
|
|
||||||
onDataLoaded(result, !result.isEmpty());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec(accountID);
|
|
||||||
}
|
|
||||||
case LIST -> {
|
|
||||||
currentRequest=new GetListTimeline(currentList.id, offset>0 ? maxID : null, null, count, null)
|
|
||||||
.setCallback(new SimpleCallback<>(this){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(List<Status> result){
|
|
||||||
if(refreshing)
|
|
||||||
list.scrollToPosition(0);
|
|
||||||
maxID=result.isEmpty() ? null : result.get(result.size()-1).id;
|
|
||||||
onDataLoaded(result, !result.isEmpty());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec(accountID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -204,19 +97,6 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
fab=view.findViewById(R.id.fab);
|
fab=view.findViewById(R.id.fab);
|
||||||
fab.setOnClickListener(this::onFabClick);
|
fab.setOnClickListener(this::onFabClick);
|
||||||
newPostsBtn=view.findViewById(R.id.new_posts_btn);
|
|
||||||
newPostsBtn.setOnClickListener(this::onNewPostsBtnClick);
|
|
||||||
newPostsBtnWrap=view.findViewById(R.id.new_posts_btn_wrap);
|
|
||||||
|
|
||||||
if(newPostsBtnShown){
|
|
||||||
newPostsBtnWrap.setVisibility(View.VISIBLE);
|
|
||||||
}else{
|
|
||||||
newPostsBtnWrap.setVisibility(View.GONE);
|
|
||||||
newPostsBtnWrap.setScaleX(0.9f);
|
|
||||||
newPostsBtnWrap.setScaleY(0.9f);
|
|
||||||
newPostsBtnWrap.setAlpha(0f);
|
|
||||||
newPostsBtnWrap.setTranslationY(V.dp(-56));
|
|
||||||
}
|
|
||||||
updateToolbarLogo();
|
updateToolbarLogo();
|
||||||
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
|
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
|
||||||
@Override
|
@Override
|
||||||
@@ -236,26 +116,13 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||||
inflater.inflate(R.menu.home, menu);
|
inflater.inflate(R.menu.home, menu);
|
||||||
menu.findItem(R.id.edit_list).setVisible(listMode==ListMode.LIST);
|
|
||||||
GithubSelfUpdater.UpdateState state=GithubSelfUpdater.UpdateState.NO_UPDATE;
|
|
||||||
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
|
|
||||||
if(updater!=null)
|
|
||||||
state=updater.getState();
|
|
||||||
if(state!=GithubSelfUpdater.UpdateState.NO_UPDATE && state!=GithubSelfUpdater.UpdateState.CHECKING)
|
|
||||||
getToolbar().getMenu().findItem(R.id.settings).setIcon(R.drawable.ic_settings_updateready_24px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item){
|
public boolean onOptionsItemSelected(MenuItem item){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
int id=item.getItemId();
|
Nav.go(getActivity(), SettingsMainFragment.class, args);
|
||||||
if(id==R.id.settings){
|
|
||||||
Nav.go(getActivity(), SettingsMainFragment.class, args);
|
|
||||||
}else if(id==R.id.edit_list){
|
|
||||||
args.putParcelable("list", Parcels.wrap(currentList));
|
|
||||||
Nav.go(getActivity(), EditListFragment.class, args);
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,7 +147,7 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||||||
@Override
|
@Override
|
||||||
protected void onHidden(){
|
protected void onHidden(){
|
||||||
super.onHidden();
|
super.onHidden();
|
||||||
if(!data.isEmpty() && listMode==ListMode.FOLLOWING){
|
if(!data.isEmpty()){
|
||||||
String topPostID=displayItems.get(Math.max(0, list.getChildAdapterPosition(list.getChildAt(0))-getMainAdapterOffset())).parentID;
|
String topPostID=displayItems.get(Math.max(0, list.getChildAdapterPosition(list.getChildAt(0))-getMainAdapterOffset())).parentID;
|
||||||
if(!topPostID.equals(lastSavedMarkerID)){
|
if(!topPostID.equals(lastSavedMarkerID)){
|
||||||
lastSavedMarkerID=topPostID;
|
lastSavedMarkerID=topPostID;
|
||||||
@@ -316,8 +183,8 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||||||
// we'll get the currently topmost post as last in the response. This way we know there's no gap
|
// we'll get the currently topmost post as last in the response. This way we know there's no gap
|
||||||
// between the existing and newly loaded parts of the timeline.
|
// between the existing and newly loaded parts of the timeline.
|
||||||
String sinceID=data.size()>1 ? data.get(1).id : "1";
|
String sinceID=data.size()>1 ? data.get(1).id : "1";
|
||||||
boolean needCache=listMode==ListMode.FOLLOWING;
|
currentRequest=new GetHomeTimeline(null, null, 20, sinceID)
|
||||||
loadAdditionalPosts(null, null, 20, sinceID, new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
currentRequest=null;
|
currentRequest=null;
|
||||||
@@ -332,13 +199,11 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||||||
result.get(result.size()-1).hasGapAfter=true;
|
result.get(result.size()-1).hasGapAfter=true;
|
||||||
toAdd=result;
|
toAdd=result;
|
||||||
}
|
}
|
||||||
if(needCache)
|
AccountSessionManager.get(accountID).filterStatuses(toAdd, FilterContext.HOME);
|
||||||
AccountSessionManager.get(accountID).filterStatuses(toAdd, FilterContext.HOME);
|
|
||||||
if(!toAdd.isEmpty()){
|
if(!toAdd.isEmpty()){
|
||||||
prependItems(toAdd, true);
|
prependItems(toAdd, true);
|
||||||
showNewPostsButton();
|
showNewPostsButton();
|
||||||
if(needCache)
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(toAdd, false);
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(toAdd, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,7 +212,8 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||||||
currentRequest=null;
|
currentRequest=null;
|
||||||
dataLoading=false;
|
dataLoading=false;
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -359,11 +225,10 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||||||
V.setVisibilityAnimated(item.text, View.GONE);
|
V.setVisibilityAnimated(item.text, View.GONE);
|
||||||
GapStatusDisplayItem gap=item.getItem();
|
GapStatusDisplayItem gap=item.getItem();
|
||||||
dataLoading=true;
|
dataLoading=true;
|
||||||
boolean needCache=listMode==ListMode.FOLLOWING;
|
currentRequest=new GetHomeTimeline(item.getItemID(), null, 20, null)
|
||||||
loadAdditionalPosts(item.getItemID(), null, 20, null, new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
|
|
||||||
currentRequest=null;
|
currentRequest=null;
|
||||||
dataLoading=false;
|
dataLoading=false;
|
||||||
if(getActivity()==null)
|
if(getActivity()==null)
|
||||||
@@ -377,8 +242,7 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||||||
Status gapStatus=getStatusByID(gap.parentID);
|
Status gapStatus=getStatusByID(gap.parentID);
|
||||||
if(gapStatus!=null){
|
if(gapStatus!=null){
|
||||||
gapStatus.hasGapAfter=false;
|
gapStatus.hasGapAfter=false;
|
||||||
if(needCache)
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false);
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false);
|
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
Set<String> idsBelowGap=new HashSet<>();
|
Set<String> idsBelowGap=new HashSet<>();
|
||||||
@@ -390,8 +254,7 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||||||
}else if(s.id.equals(gap.parentID)){
|
}else if(s.id.equals(gap.parentID)){
|
||||||
belowGap=true;
|
belowGap=true;
|
||||||
s.hasGapAfter=false;
|
s.hasGapAfter=false;
|
||||||
if(needCache)
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false);
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false);
|
|
||||||
}else{
|
}else{
|
||||||
gapPostIndex++;
|
gapPostIndex++;
|
||||||
}
|
}
|
||||||
@@ -407,8 +270,7 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||||||
}else{
|
}else{
|
||||||
result=result.subList(0, endIndex);
|
result=result.subList(0, endIndex);
|
||||||
}
|
}
|
||||||
if(needCache)
|
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.HOME);
|
||||||
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.HOME);
|
|
||||||
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
||||||
targetList.clear();
|
targetList.clear();
|
||||||
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
||||||
@@ -425,8 +287,7 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||||||
adapter.notifyItemChanged(getMainAdapterOffset()+gapPos);
|
adapter.notifyItemChanged(getMainAdapterOffset()+gapPos);
|
||||||
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1);
|
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1);
|
||||||
}
|
}
|
||||||
if(needCache)
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -443,17 +304,9 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||||||
adapter.notifyItemChanged(gapPos);
|
adapter.notifyItemChanged(gapPos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
.exec(accountID);
|
||||||
|
|
||||||
private void loadAdditionalPosts(String maxID, String minID, int limit, String sinceID, Callback<List<Status>> callback){
|
|
||||||
MastodonAPIRequest<List<Status>> req=switch(listMode){
|
|
||||||
case FOLLOWING -> new GetHomeTimeline(maxID, minID, limit, sinceID);
|
|
||||||
case LOCAL -> new GetPublicTimeline(true, false, maxID, minID, limit, sinceID);
|
|
||||||
case LIST -> new GetListTimeline(currentList.id, maxID, minID, limit, sinceID);
|
|
||||||
};
|
|
||||||
currentRequest=req;
|
|
||||||
req.setCallback(callback).exec(accountID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -467,41 +320,42 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateToolbarLogo(){
|
private void updateToolbarLogo(){
|
||||||
listsDropdown=new LinearLayout(getActivity());
|
toolbarLogo=new ImageView(getActivity());
|
||||||
listsDropdown.setOnClickListener(this::onListsDropdownClick);
|
toolbarLogo.setScaleType(ImageView.ScaleType.CENTER);
|
||||||
listsDropdown.setBackgroundResource(R.drawable.bg_button_m3_text);
|
toolbarLogo.setImageResource(R.drawable.logo);
|
||||||
listsDropdown.setAccessibilityDelegate(new View.AccessibilityDelegate(){
|
toolbarLogo.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary)));
|
||||||
@Override
|
|
||||||
public void onInitializeAccessibilityNodeInfo(@NonNull View host, @NonNull AccessibilityNodeInfo info){
|
toolbarShowNewPostsBtn=new Button(getActivity());
|
||||||
super.onInitializeAccessibilityNodeInfo(host, info);
|
toolbarShowNewPostsBtn.setTextAppearance(R.style.m3_title_medium);
|
||||||
info.setClassName("android.widget.Spinner");
|
toolbarShowNewPostsBtn.setTextColor(0xffffffff);
|
||||||
}
|
toolbarShowNewPostsBtn.setStateListAnimator(null);
|
||||||
});
|
toolbarShowNewPostsBtn.setBackgroundResource(R.drawable.bg_button_new_posts);
|
||||||
listsDropdownArrow=new FixedAspectRatioImageView(getActivity());
|
toolbarShowNewPostsBtn.setText(R.string.see_new_posts);
|
||||||
listsDropdownArrow.setUseHeight(true);
|
toolbarShowNewPostsBtn.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_fluent_arrow_up_16_filled, 0, 0, 0);
|
||||||
listsDropdownArrow.setImageResource(R.drawable.ic_arrow_drop_down_24px);
|
toolbarShowNewPostsBtn.setCompoundDrawableTintList(toolbarShowNewPostsBtn.getTextColors());
|
||||||
listsDropdownArrow.setScaleType(ImageView.ScaleType.CENTER);
|
toolbarShowNewPostsBtn.setCompoundDrawablePadding(V.dp(8));
|
||||||
listsDropdownArrow.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
|
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N)
|
||||||
listsDropdown.addView(listsDropdownArrow, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
UiUtils.fixCompoundDrawableTintOnAndroid6(toolbarShowNewPostsBtn);
|
||||||
listsDropdownText=new TextView(getActivity());
|
toolbarShowNewPostsBtn.setOnClickListener(this::onNewPostsBtnClick);
|
||||||
listsDropdownText.setTextAppearance(R.style.action_bar_title);
|
|
||||||
listsDropdownText.setSingleLine();
|
if(newPostsBtnShown){
|
||||||
listsDropdownText.setEllipsize(TextUtils.TruncateAt.END);
|
toolbarShowNewPostsBtn.setVisibility(View.VISIBLE);
|
||||||
listsDropdownText.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
|
toolbarLogo.setVisibility(View.INVISIBLE);
|
||||||
listsDropdownText.setPaddingRelative(V.dp(4), 0, V.dp(16), 0);
|
toolbarLogo.setAlpha(0f);
|
||||||
listsDropdownText.setText(getCurrentListTitle());
|
}else{
|
||||||
listsDropdownArrow.setImageTintList(listsDropdownText.getTextColors());
|
toolbarShowNewPostsBtn.setVisibility(View.INVISIBLE);
|
||||||
listsDropdown.setBackgroundTintList(listsDropdownText.getTextColors());
|
toolbarShowNewPostsBtn.setAlpha(0f);
|
||||||
listsDropdown.addView(listsDropdownText, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
toolbarShowNewPostsBtn.setScaleX(.8f);
|
||||||
|
toolbarShowNewPostsBtn.setScaleY(.8f);
|
||||||
|
toolbarLogo.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
FrameLayout logoWrap=new FrameLayout(getActivity());
|
FrameLayout logoWrap=new FrameLayout(getActivity());
|
||||||
FrameLayout.LayoutParams ddlp=new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT, Gravity.START);
|
logoWrap.addView(toolbarLogo, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
|
||||||
ddlp.topMargin=ddlp.bottomMargin=V.dp(8);
|
logoWrap.addView(toolbarShowNewPostsBtn, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, V.dp(32), Gravity.CENTER));
|
||||||
logoWrap.addView(listsDropdown, ddlp);
|
|
||||||
|
|
||||||
Toolbar toolbar=getToolbar();
|
Toolbar toolbar=getToolbar();
|
||||||
toolbar.addView(logoWrap, new Toolbar.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
toolbar.addView(logoWrap, new Toolbar.LayoutParams(Gravity.CENTER));
|
||||||
toolbar.setContentInsetsRelative(V.dp(16), 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showNewPostsButton(){
|
private void showNewPostsButton(){
|
||||||
@@ -511,19 +365,20 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||||||
if(currentNewPostsAnim!=null){
|
if(currentNewPostsAnim!=null){
|
||||||
currentNewPostsAnim.cancel();
|
currentNewPostsAnim.cancel();
|
||||||
}
|
}
|
||||||
newPostsBtnWrap.setVisibility(View.VISIBLE);
|
toolbarShowNewPostsBtn.setVisibility(View.VISIBLE);
|
||||||
AnimatorSet set=new AnimatorSet();
|
AnimatorSet set=new AnimatorSet();
|
||||||
set.playTogether(
|
set.playTogether(
|
||||||
ObjectAnimator.ofFloat(newPostsBtnWrap, View.ALPHA, 1f),
|
ObjectAnimator.ofFloat(toolbarLogo, View.ALPHA, 0f),
|
||||||
ObjectAnimator.ofFloat(newPostsBtnWrap, View.SCALE_X, 1f),
|
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.ALPHA, 1f),
|
||||||
ObjectAnimator.ofFloat(newPostsBtnWrap, View.SCALE_Y, 1f),
|
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_X, 1f),
|
||||||
ObjectAnimator.ofFloat(newPostsBtnWrap, View.TRANSLATION_Y, 0f)
|
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_Y, 1f)
|
||||||
);
|
);
|
||||||
set.setDuration(getResources().getInteger(R.integer.m3_sys_motion_duration_medium3));
|
set.setDuration(300);
|
||||||
set.setInterpolator(AnimationUtils.loadInterpolator(getActivity(), R.interpolator.m3_sys_motion_easing_standard_decelerate));
|
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||||
set.addListener(new AnimatorListenerAdapter(){
|
set.addListener(new AnimatorListenerAdapter(){
|
||||||
@Override
|
@Override
|
||||||
public void onAnimationEnd(Animator animation){
|
public void onAnimationEnd(Animator animation){
|
||||||
|
toolbarLogo.setVisibility(View.INVISIBLE);
|
||||||
currentNewPostsAnim=null;
|
currentNewPostsAnim=null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -538,19 +393,20 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||||||
if(currentNewPostsAnim!=null){
|
if(currentNewPostsAnim!=null){
|
||||||
currentNewPostsAnim.cancel();
|
currentNewPostsAnim.cancel();
|
||||||
}
|
}
|
||||||
|
toolbarLogo.setVisibility(View.VISIBLE);
|
||||||
AnimatorSet set=new AnimatorSet();
|
AnimatorSet set=new AnimatorSet();
|
||||||
set.playTogether(
|
set.playTogether(
|
||||||
ObjectAnimator.ofFloat(newPostsBtnWrap, View.ALPHA, 0f),
|
ObjectAnimator.ofFloat(toolbarLogo, View.ALPHA, 1f),
|
||||||
ObjectAnimator.ofFloat(newPostsBtnWrap, View.SCALE_X, .9f),
|
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.ALPHA, 0f),
|
||||||
ObjectAnimator.ofFloat(newPostsBtnWrap, View.SCALE_Y, .9f),
|
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_X, .8f),
|
||||||
ObjectAnimator.ofFloat(newPostsBtnWrap, View.TRANSLATION_Y, V.dp(-56))
|
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_Y, .8f)
|
||||||
);
|
);
|
||||||
set.setDuration(getResources().getInteger(R.integer.m3_sys_motion_duration_medium3));
|
set.setDuration(300);
|
||||||
set.setInterpolator(AnimationUtils.loadInterpolator(getActivity(), R.interpolator.m3_sys_motion_easing_standard_accelerate));
|
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||||
set.addListener(new AnimatorListenerAdapter(){
|
set.addListener(new AnimatorListenerAdapter(){
|
||||||
@Override
|
@Override
|
||||||
public void onAnimationEnd(Animator animation){
|
public void onAnimationEnd(Animator animation){
|
||||||
newPostsBtnWrap.setVisibility(View.GONE);
|
toolbarShowNewPostsBtn.setVisibility(View.INVISIBLE);
|
||||||
currentNewPostsAnim=null;
|
currentNewPostsAnim=null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -565,20 +421,6 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onListsDropdownClick(View v){
|
|
||||||
listsDropdownArrow.animate().rotation(-180f).setDuration(150).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
|
||||||
dropdownController.show(dropdownMainMenuController);
|
|
||||||
AccountSessionManager.get(accountID).getCacheController().reloadLists(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(java.util.List<FollowList> result){
|
|
||||||
lists=result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView(){
|
public void onDestroyView(){
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
@@ -601,67 +443,4 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||||||
protected boolean shouldRemoveAccountPostsWhenUnfollowing(){
|
protected boolean shouldRemoveAccountPostsWhenUnfollowing(){
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Toolbar getToolbar(){
|
|
||||||
return super.getToolbar();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDropdownWillDismiss(){
|
|
||||||
listsDropdownArrow.animate().rotation(0f).setDuration(150).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDropdownDismissed(){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reload(){
|
|
||||||
if(currentRequest!=null){
|
|
||||||
currentRequest.cancel();
|
|
||||||
currentRequest=null;
|
|
||||||
}
|
|
||||||
refreshing=true;
|
|
||||||
showProgress();
|
|
||||||
loadData();
|
|
||||||
listsDropdownText.setText(getCurrentListTitle());
|
|
||||||
invalidateOptionsMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected RecyclerView.Adapter getAdapter(){
|
|
||||||
mergeAdapter=new MergeRecyclerAdapter();
|
|
||||||
mergeAdapter.addAdapter(super.getAdapter());
|
|
||||||
return mergeAdapter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDataLoaded(List<Status> d, boolean more){
|
|
||||||
if(refreshing){
|
|
||||||
if(listMode==ListMode.LOCAL){
|
|
||||||
localTimelineBannerHelper.maybeAddBanner(list, mergeAdapter);
|
|
||||||
localTimelineBannerHelper.onBannerBecameVisible();
|
|
||||||
}else{
|
|
||||||
localTimelineBannerHelper.removeBanner(mergeAdapter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.onDataLoaded(d, more);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getCurrentListTitle(){
|
|
||||||
return switch(listMode){
|
|
||||||
case FOLLOWING -> getString(R.string.timeline_following);
|
|
||||||
case LOCAL -> getString(R.string.local_timeline);
|
|
||||||
case LIST -> currentList.title;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum ListMode{
|
|
||||||
FOLLOWING,
|
|
||||||
LOCAL,
|
|
||||||
LIST
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,301 +0,0 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.ActionMode;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.WindowInsets;
|
|
||||||
import android.widget.ImageButton;
|
|
||||||
|
|
||||||
import com.squareup.otto.Subscribe;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
|
||||||
import org.joinmastodon.android.api.requests.lists.AddAccountsToList;
|
|
||||||
import org.joinmastodon.android.api.requests.lists.GetListAccounts;
|
|
||||||
import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
|
|
||||||
import org.joinmastodon.android.events.AccountAddedToListEvent;
|
|
||||||
import org.joinmastodon.android.events.AccountRemovedFromListEvent;
|
|
||||||
import org.joinmastodon.android.fragments.account_list.AddListMembersFragment;
|
|
||||||
import org.joinmastodon.android.fragments.account_list.PaginatedAccountListFragment;
|
|
||||||
import org.joinmastodon.android.model.Account;
|
|
||||||
import org.joinmastodon.android.model.FollowList;
|
|
||||||
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
|
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
|
||||||
import org.joinmastodon.android.ui.utils.ActionModeHelper;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
|
|
||||||
import org.parceler.Parcels;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
|
||||||
import me.grishka.appkit.api.Callback;
|
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
|
||||||
import me.grishka.appkit.utils.V;
|
|
||||||
|
|
||||||
public class ListMembersFragment extends PaginatedAccountListFragment{
|
|
||||||
private static final int ADD_MEMBER_RESULT=600;
|
|
||||||
|
|
||||||
private ImageButton fab;
|
|
||||||
private FollowList followList;
|
|
||||||
private boolean inSelectionMode;
|
|
||||||
private Set<String> selectedAccounts=new HashSet<>();
|
|
||||||
private ActionMode actionMode;
|
|
||||||
private MenuItem deleteItem;
|
|
||||||
|
|
||||||
public ListMembersFragment(){
|
|
||||||
setListLayoutId(R.layout.recycler_fragment_with_fab);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState){
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
followList=Parcels.unwrap(getArguments().getParcelable("list"));
|
|
||||||
setTitle(R.string.list_members);
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
E.register(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy(){
|
|
||||||
super.onDestroy();
|
|
||||||
E.unregister(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
|
||||||
return new GetListAccounts(followList.id, maxID, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onConfigureViewHolder(AccountViewHolder holder){
|
|
||||||
super.onConfigureViewHolder(holder);
|
|
||||||
holder.setStyle(inSelectionMode ? AccountViewHolder.AccessoryType.CHECKBOX : AccountViewHolder.AccessoryType.MENU, false);
|
|
||||||
holder.setOnClickListener(this::onItemClick);
|
|
||||||
holder.setOnLongClickListener(this::onItemLongClick);
|
|
||||||
holder.getContextMenu().getMenu().add(0, R.id.remove_from_list, 0, R.string.remove_from_list);
|
|
||||||
holder.setOnCustomMenuItemSelectedListener(item->onItemMenuItemSelected(holder, item));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onBindViewHolder(AccountViewHolder holder){
|
|
||||||
super.onBindViewHolder(holder);
|
|
||||||
holder.setStyle(inSelectionMode ? AccountViewHolder.AccessoryType.CHECKBOX : AccountViewHolder.AccessoryType.MENU, false);
|
|
||||||
if(inSelectionMode){
|
|
||||||
holder.setChecked(selectedAccounts.contains(holder.getItem().account.id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean wantsLightStatusBar(){
|
|
||||||
if(actionMode!=null)
|
|
||||||
return UiUtils.isDarkTheme();
|
|
||||||
return super.wantsLightStatusBar();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
|
||||||
inflater.inflate(R.menu.selectable_list, menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item){
|
|
||||||
int id=item.getItemId();
|
|
||||||
if(id==R.id.select){
|
|
||||||
enterSelectionMode();
|
|
||||||
}else if(id==R.id.select_all){
|
|
||||||
for(AccountViewModel a:data){
|
|
||||||
selectedAccounts.add(a.account.id);
|
|
||||||
}
|
|
||||||
enterSelectionMode();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
fab=view.findViewById(R.id.fab);
|
|
||||||
fab.setImageResource(R.drawable.ic_add_24px);
|
|
||||||
fab.setContentDescription(getString(R.string.add_list_member));
|
|
||||||
fab.setOnClickListener(v->onFabClick());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onApplyWindowInsets(WindowInsets insets){
|
|
||||||
super.onApplyWindowInsets(insets);
|
|
||||||
UiUtils.applyBottomInsetToFAB(fab, insets);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFragmentResult(int reqCode, boolean success, Bundle result){
|
|
||||||
if(reqCode==ADD_MEMBER_RESULT && success){
|
|
||||||
Account acc=Objects.requireNonNull(Parcels.unwrap(result.getParcelable("selectedAccount")));
|
|
||||||
addAccounts(List.of(acc));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onAccountRemovedFromList(AccountRemovedFromListEvent ev){
|
|
||||||
if(ev.accountID.equals(accountID) && ev.listID.equals(followList.id)){
|
|
||||||
removeAccountRows(Set.of(ev.targetAccountID));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onAccountAddedToList(AccountAddedToListEvent ev){
|
|
||||||
if(ev.accountID.equals(accountID) && ev.listID.equals(followList.id)){
|
|
||||||
data.add(new AccountViewModel(ev.account, accountID));
|
|
||||||
list.getAdapter().notifyItemInserted(data.size()-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onFabClick(){
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
Nav.goForResult(getActivity(), AddListMembersFragment.class, args, ADD_MEMBER_RESULT, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onItemClick(AccountViewHolder holder){
|
|
||||||
if(inSelectionMode){
|
|
||||||
String id=holder.getItem().account.id;
|
|
||||||
if(selectedAccounts.contains(id)){
|
|
||||||
selectedAccounts.remove(id);
|
|
||||||
holder.setChecked(false);
|
|
||||||
}else{
|
|
||||||
selectedAccounts.add(id);
|
|
||||||
holder.setChecked(true);
|
|
||||||
}
|
|
||||||
updateActionModeTitle();
|
|
||||||
deleteItem.setEnabled(!selectedAccounts.isEmpty());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
args.putParcelable("profileAccount", Parcels.wrap(holder.getItem().account));
|
|
||||||
Nav.go(getActivity(), ProfileFragment.class, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean onItemLongClick(AccountViewHolder holder){
|
|
||||||
if(inSelectionMode)
|
|
||||||
return false;
|
|
||||||
selectedAccounts.add(holder.getItem().account.id);
|
|
||||||
enterSelectionMode();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onItemMenuItemSelected(AccountViewHolder holder, MenuItem item){
|
|
||||||
int id=item.getItemId();
|
|
||||||
if(id==R.id.remove_from_list){
|
|
||||||
new M3AlertDialogBuilder(getActivity())
|
|
||||||
.setTitle(R.string.confirm_remove_list_member)
|
|
||||||
.setPositiveButton(R.string.remove, (dlg, which)->removeAccounts(Set.of(holder.getItem().account.id)))
|
|
||||||
.setNegativeButton(R.string.cancel, null)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateItemsForSelectionModeTransition(){
|
|
||||||
list.getAdapter().notifyItemRangeChanged(0, data.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void enterSelectionMode(){
|
|
||||||
inSelectionMode=true;
|
|
||||||
updateItemsForSelectionModeTransition();
|
|
||||||
V.setVisibilityAnimated(fab, View.INVISIBLE);
|
|
||||||
actionMode=ActionModeHelper.startActionMode(this, ()->elevationOnScrollListener.getCurrentStatusBarColor(), new ActionMode.Callback(){
|
|
||||||
@Override
|
|
||||||
public boolean onCreateActionMode(ActionMode mode, Menu menu){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu){
|
|
||||||
mode.getMenuInflater().inflate(R.menu.settings_filter_words_action_mode, menu);
|
|
||||||
deleteItem=menu.findItem(R.id.delete);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item){
|
|
||||||
new M3AlertDialogBuilder(getActivity())
|
|
||||||
.setTitle(R.string.confirm_remove_list_members)
|
|
||||||
.setPositiveButton(R.string.remove, (dlg, which)->removeAccounts(new HashSet<>(selectedAccounts)))
|
|
||||||
.setNegativeButton(R.string.cancel, null)
|
|
||||||
.show();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyActionMode(ActionMode mode){
|
|
||||||
actionMode=null;
|
|
||||||
inSelectionMode=false;
|
|
||||||
selectedAccounts.clear();
|
|
||||||
updateItemsForSelectionModeTransition();
|
|
||||||
V.setVisibilityAnimated(fab, View.VISIBLE);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
updateActionModeTitle();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateActionModeTitle(){
|
|
||||||
actionMode.setTitle(getResources().getQuantityString(R.plurals.x_items_selected, selectedAccounts.size(), selectedAccounts.size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeAccounts(Set<String> ids){
|
|
||||||
new RemoveAccountsFromList(followList.id, ids)
|
|
||||||
.setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Void result){
|
|
||||||
if(inSelectionMode)
|
|
||||||
actionMode.finish();
|
|
||||||
removeAccountRows(ids);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
error.showToast(getActivity());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.wrapProgress(getActivity(), R.string.loading, true)
|
|
||||||
.exec(accountID);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addAccounts(Collection<Account> accounts){
|
|
||||||
new AddAccountsToList(followList.id, accounts.stream().map(a->a.id).collect(Collectors.toSet()))
|
|
||||||
.setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Void result){
|
|
||||||
for(Account acc:accounts){
|
|
||||||
data.add(new AccountViewModel(acc, accountID));
|
|
||||||
}
|
|
||||||
list.getAdapter().notifyItemRangeInserted(data.size()-accounts.size(), accounts.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
error.showToast(getActivity());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.wrapProgress(getActivity(), R.string.loading, true)
|
|
||||||
.exec(accountID);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeAccountRows(Set<String> ids){
|
|
||||||
for(int i=data.size()-1;i>=0;i--){
|
|
||||||
if(ids.contains(data.get(i).account.id)){
|
|
||||||
data.remove(i);
|
|
||||||
list.getAdapter().notifyItemRemoved(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetListTimeline;
|
|
||||||
import org.joinmastodon.android.model.FollowList;
|
|
||||||
import org.joinmastodon.android.model.Status;
|
|
||||||
import org.parceler.Parcels;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
|
||||||
|
|
||||||
public class ListTimelineFragment extends StatusListFragment{
|
|
||||||
private FollowList followList;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState){
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
followList=Parcels.unwrap(getArguments().getParcelable("list"));
|
|
||||||
setTitle(followList.title);
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
loadData();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doLoadData(int offset, int count){
|
|
||||||
currentRequest=new GetListTimeline(followList.id, offset>0 ? getMaxID() : null, null, count, null)
|
|
||||||
.setCallback(new SimpleCallback<>(this){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(List<Status> result){
|
|
||||||
onDataLoaded(result, !result.isEmpty());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec(accountID);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
|
||||||
inflater.inflate(R.menu.standalone_list_timeline, menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item){
|
|
||||||
int id=item.getItemId();
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
args.putParcelable("list", Parcels.wrap(followList));
|
|
||||||
if(id==R.id.members){
|
|
||||||
Nav.go(getActivity(), ListMembersFragment.class, args);
|
|
||||||
}else if(id==R.id.edit_list){
|
|
||||||
Nav.go(getActivity(), EditListFragment.class, args);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.tags.GetFollowedTags;
|
|
||||||
import org.joinmastodon.android.api.requests.tags.SetTagFollowed;
|
|
||||||
import org.joinmastodon.android.fragments.settings.BaseSettingsFragment;
|
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
|
||||||
import org.joinmastodon.android.model.viewmodel.ListItemWithOptionsMenu;
|
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import me.grishka.appkit.api.Callback;
|
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
|
||||||
|
|
||||||
public class ManageFollowedHashtagsFragment extends BaseSettingsFragment<Hashtag> implements ListItemWithOptionsMenu.OptionsMenuListener<Hashtag>{
|
|
||||||
private String maxID;
|
|
||||||
|
|
||||||
public ManageFollowedHashtagsFragment(){
|
|
||||||
super(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState){
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setTitle(R.string.manage_hashtags);
|
|
||||||
loadData();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doLoadData(int offset, int count){
|
|
||||||
currentRequest=new GetFollowedTags(offset>0 ? maxID : null, count)
|
|
||||||
.setCallback(new SimpleCallback<>(this){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(HeaderPaginationList<Hashtag> result){
|
|
||||||
maxID=null;
|
|
||||||
if(result.nextPageUri!=null)
|
|
||||||
maxID=result.nextPageUri.getQueryParameter("max_id");
|
|
||||||
onDataLoaded(result.stream().map(t->{
|
|
||||||
int posts=t.getWeekPosts();
|
|
||||||
return new ListItemWithOptionsMenu<>(t.name, getResources().getQuantityString(R.plurals.x_posts_recently, posts, posts), ManageFollowedHashtagsFragment.this,
|
|
||||||
R.drawable.ic_tag_24px, ManageFollowedHashtagsFragment.this::onItemClick, t, false);
|
|
||||||
}).collect(Collectors.toList()), maxID!=null);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec(accountID);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConfigureListItemOptionsMenu(ListItemWithOptionsMenu<Hashtag> item, Menu menu){
|
|
||||||
menu.clear();
|
|
||||||
menu.add(getString(R.string.unfollow_user, "#"+item.parentObject.name));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onListItemOptionSelected(ListItemWithOptionsMenu<Hashtag> item, MenuItem menuItem){
|
|
||||||
new M3AlertDialogBuilder(getActivity())
|
|
||||||
.setTitle(getString(R.string.unfollow_confirmation, "#"+item.parentObject.name))
|
|
||||||
.setPositiveButton(R.string.unfollow, (dlg, which)->doUnfollow(item))
|
|
||||||
.setNegativeButton(R.string.cancel, null)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onItemClick(ListItemWithOptionsMenu<Hashtag> item){
|
|
||||||
UiUtils.openHashtagTimeline(getActivity(), accountID, item.parentObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doUnfollow(ListItemWithOptionsMenu<Hashtag> item){
|
|
||||||
new SetTagFollowed(item.parentObject.name, false)
|
|
||||||
.setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Hashtag result){
|
|
||||||
int index=data.indexOf(item);
|
|
||||||
if(index==-1)
|
|
||||||
return;
|
|
||||||
data.remove(index);
|
|
||||||
list.getAdapter().notifyItemRemoved(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
error.showToast(getActivity());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.wrapProgress(getActivity(), R.string.loading, true)
|
|
||||||
.exec(accountID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,199 +0,0 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.WindowInsets;
|
|
||||||
import android.widget.ImageButton;
|
|
||||||
|
|
||||||
import com.squareup.otto.Subscribe;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.lists.DeleteList;
|
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
|
||||||
import org.joinmastodon.android.events.ListCreatedEvent;
|
|
||||||
import org.joinmastodon.android.events.ListDeletedEvent;
|
|
||||||
import org.joinmastodon.android.events.ListUpdatedEvent;
|
|
||||||
import org.joinmastodon.android.fragments.settings.BaseSettingsFragment;
|
|
||||||
import org.joinmastodon.android.model.FollowList;
|
|
||||||
import org.joinmastodon.android.model.viewmodel.ListItem;
|
|
||||||
import org.joinmastodon.android.model.viewmodel.ListItemWithOptionsMenu;
|
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
import org.parceler.Parcels;
|
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
|
||||||
import me.grishka.appkit.api.Callback;
|
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
|
||||||
|
|
||||||
public class ManageListsFragment extends BaseSettingsFragment<FollowList> implements ListItemWithOptionsMenu.OptionsMenuListener<FollowList>{
|
|
||||||
private ImageButton fab;
|
|
||||||
|
|
||||||
public ManageListsFragment(){
|
|
||||||
setListLayoutId(R.layout.recycler_fragment_with_fab);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState){
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setTitle(R.string.manage_lists);
|
|
||||||
loadData();
|
|
||||||
setRefreshEnabled(true);
|
|
||||||
E.register(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy(){
|
|
||||||
super.onDestroy();
|
|
||||||
E.unregister(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doLoadData(int offset, int count){
|
|
||||||
Callback<List<FollowList>> callback=new SimpleCallback<>(this){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(List<FollowList> result){
|
|
||||||
onDataLoaded(result.stream().map(ManageListsFragment.this::makeItem).collect(Collectors.toList()), false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if(refreshing){
|
|
||||||
AccountSessionManager.get(accountID)
|
|
||||||
.getCacheController()
|
|
||||||
.reloadLists(callback);
|
|
||||||
}else{
|
|
||||||
AccountSessionManager.get(accountID)
|
|
||||||
.getCacheController()
|
|
||||||
.getLists(callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ListItem<FollowList> makeItem(FollowList l){
|
|
||||||
return new ListItemWithOptionsMenu<>(l.title, null, ManageListsFragment.this, R.drawable.ic_list_alt_24px, ManageListsFragment.this::onListClick, l, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onListClick(ListItemWithOptionsMenu<FollowList> item){
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
args.putParcelable("list", Parcels.wrap(item.parentObject));
|
|
||||||
Nav.go(getActivity(), ListTimelineFragment.class, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConfigureListItemOptionsMenu(ListItemWithOptionsMenu<FollowList> item, Menu menu){
|
|
||||||
menu.add(0, R.id.edit, 0, R.string.edit_list);
|
|
||||||
menu.add(0, R.id.delete, 1, R.string.delete_list);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onListItemOptionSelected(ListItemWithOptionsMenu<FollowList> item, MenuItem menuItem){
|
|
||||||
int id=menuItem.getItemId();
|
|
||||||
if(id==R.id.edit){
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
args.putParcelable("list", Parcels.wrap(item.parentObject));
|
|
||||||
Nav.go(getActivity(), EditListFragment.class, args);
|
|
||||||
}else if(id==R.id.delete){
|
|
||||||
new M3AlertDialogBuilder(getActivity())
|
|
||||||
.setTitle(R.string.delete_list)
|
|
||||||
.setMessage(getString(R.string.delete_list_confirm, item.parentObject.title))
|
|
||||||
.setPositiveButton(R.string.delete, (dlg, which)->doDeleteList(item.parentObject))
|
|
||||||
.setNegativeButton(R.string.cancel, null)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
fab=view.findViewById(R.id.fab);
|
|
||||||
fab.setImageResource(R.drawable.ic_add_24px);
|
|
||||||
fab.setContentDescription(getString(R.string.create_list));
|
|
||||||
fab.setOnClickListener(v->onFabClick());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onApplyWindowInsets(WindowInsets insets){
|
|
||||||
super.onApplyWindowInsets(insets);
|
|
||||||
UiUtils.applyBottomInsetToFAB(fab, insets);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doDeleteList(FollowList list){
|
|
||||||
new DeleteList(list.id)
|
|
||||||
.setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Void result){
|
|
||||||
for(int i=0;i<data.size();i++){
|
|
||||||
if(data.get(i).parentObject==list){
|
|
||||||
data.remove(i);
|
|
||||||
itemsAdapter.notifyItemRemoved(i);
|
|
||||||
AccountSessionManager.get(accountID).getCacheController().deleteList(list.id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
Activity activity=getActivity();
|
|
||||||
if(activity==null)
|
|
||||||
return;
|
|
||||||
error.showToast(activity);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.wrapProgress(getActivity(), R.string.loading, true)
|
|
||||||
.exec(accountID);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onListUpdated(ListUpdatedEvent ev){
|
|
||||||
if(!ev.accountID.equals(accountID))
|
|
||||||
return;
|
|
||||||
for(ListItem<FollowList> item:data){
|
|
||||||
if(item.parentObject.id.equals(ev.list.id)){
|
|
||||||
item.parentObject=ev.list;
|
|
||||||
item.title=ev.list.title;
|
|
||||||
rebindItem(item);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onListDeleted(ListDeletedEvent ev){
|
|
||||||
if(!ev.accountID.equals(accountID))
|
|
||||||
return;
|
|
||||||
int i=0;
|
|
||||||
for(ListItem<FollowList> item:data){
|
|
||||||
if(item.parentObject.id.equals(ev.listID)){
|
|
||||||
data.remove(i);
|
|
||||||
itemsAdapter.notifyItemRemoved(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onListCreated(ListCreatedEvent ev){
|
|
||||||
if(!ev.accountID.equals(accountID))
|
|
||||||
return;
|
|
||||||
ListItem<FollowList> item=makeItem(ev.list);
|
|
||||||
data.add(item);
|
|
||||||
((List<ListItem<FollowList>>)data).sort(Comparator.comparing(l->l.parentObject.title));
|
|
||||||
itemsAdapter.notifyItemInserted(data.indexOf(item));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onFabClick(){
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
Nav.go(getActivity(), CreateListFragment.class, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,10 +2,13 @@ package org.joinmastodon.android.fragments;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||||
|
|
||||||
@@ -36,21 +39,22 @@ public abstract class MastodonRecyclerFragment<T> extends BaseRecyclerFragment<T
|
|||||||
@CallSuper
|
@CallSuper
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
if(wantsElevationOnScrollEffect()){
|
if(wantsElevationOnScrollEffect())
|
||||||
FragmentRootLinearLayout rootView;
|
list.addOnScrollListener(elevationOnScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, getViewsForElevationEffect()));
|
||||||
if(view instanceof FragmentRootLinearLayout frl)
|
|
||||||
rootView=frl;
|
|
||||||
else
|
|
||||||
rootView=view.findViewById(R.id.appkit_loader_root);
|
|
||||||
list.addOnScrollListener(elevationOnScrollListener=new ElevationOnScrollListener(rootView, getViewsForElevationEffect()));
|
|
||||||
}
|
|
||||||
list.setItemAnimator(new BetterItemAnimator());
|
|
||||||
if(refreshLayout!=null){
|
if(refreshLayout!=null){
|
||||||
int colorBackground=UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background);
|
int colorBackground=UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background);
|
||||||
int colorPrimary=UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary);
|
int colorPrimary=UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary);
|
||||||
refreshLayout.setProgressBackgroundColorSchemeColor(UiUtils.alphaBlendColors(colorBackground, colorPrimary, 0.11f));
|
refreshLayout.setProgressBackgroundColorSchemeColor(UiUtils.alphaBlendColors(colorBackground, colorPrimary, 0.11f));
|
||||||
refreshLayout.setColorSchemeColors(colorPrimary);
|
refreshLayout.setColorSchemeColors(colorPrimary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is to set the color of the 'This list is empty'
|
||||||
|
for (int i=0; i < ((LinearLayout) emptyView).getChildCount(); i++) {
|
||||||
|
View v = ((LinearLayout) emptyView).getChildAt(i);
|
||||||
|
if(v instanceof TextView) {
|
||||||
|
((TextView) v).setTextColor(UiUtils.getThemeColor(getContext(), android.R.attr.textColorSecondary));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ import me.grishka.appkit.Nav;
|
|||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
|
||||||
public class NotificationsListFragment extends BaseStatusListFragment<Notification>{
|
public class NotificationsListFragment extends BaseStatusListFragment<Notification>{
|
||||||
private boolean onlyMentions;
|
private boolean onlyMentions=true;
|
||||||
private String maxID;
|
private String maxID;
|
||||||
private View tabBar;
|
private View tabBar;
|
||||||
private View mentionsTab, allTab;
|
private View mentionsTab, allTab;
|
||||||
@@ -58,7 +58,9 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setLayout(R.layout.fragment_notifications);
|
setLayout(R.layout.fragment_notifications);
|
||||||
E.register(this);
|
E.register(this);
|
||||||
onlyMentions=AccountSessionManager.get(accountID).isNotificationsMentionsOnly();
|
if(savedInstanceState!=null){
|
||||||
|
onlyMentions=savedInstanceState.getBoolean("onlyMentions", true);
|
||||||
|
}
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,9 +132,13 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
protected void onShown(){
|
protected void onShown(){
|
||||||
super.onShown();
|
super.onShown();
|
||||||
unreadMarker=realUnreadMarker=AccountSessionManager.get(accountID).getLastKnownNotificationsMarker();
|
unreadMarker=realUnreadMarker=AccountSessionManager.get(accountID).getLastKnownNotificationsMarker();
|
||||||
if(!dataLoading && canRefreshWithoutUpsettingUser()){
|
if(!dataLoading){
|
||||||
reloadingFromCache=true;
|
if(onlyMentions){
|
||||||
refresh();
|
refresh();
|
||||||
|
}else{
|
||||||
|
reloadingFromCache=true;
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,6 +221,12 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
return views;
|
return views;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState){
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
outState.putBoolean("onlyMentions", onlyMentions);
|
||||||
|
}
|
||||||
|
|
||||||
private Notification getNotificationByID(String id){
|
private Notification getNotificationByID(String id){
|
||||||
for(Notification n:data){
|
for(Notification n:data){
|
||||||
if(n.id.equals(id))
|
if(n.id.equals(id))
|
||||||
@@ -279,10 +291,8 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
allTab.setSelected(!onlyMentions);
|
allTab.setSelected(!onlyMentions);
|
||||||
maxID=null;
|
maxID=null;
|
||||||
showProgress();
|
showProgress();
|
||||||
refreshing=true;
|
|
||||||
reloadingFromCache=true;
|
|
||||||
loadData(0, 20);
|
loadData(0, 20);
|
||||||
AccountSessionManager.get(accountID).setNotificationsMentionsOnly(onlyMentions);
|
refreshing=true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -302,6 +312,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||||
inflater.inflate(R.menu.notifications, menu);
|
inflater.inflate(R.menu.notifications, menu);
|
||||||
markAllReadItem=menu.findItem(R.id.mark_all_read);
|
markAllReadItem=menu.findItem(R.id.mark_all_read);
|
||||||
|
updateMarkAllReadButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -319,6 +330,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
new SaveMarkers(null, id).exec(accountID);
|
new SaveMarkers(null, id).exec(accountID);
|
||||||
AccountSessionManager.get(accountID).setNotificationsMarker(id, true);
|
AccountSessionManager.get(accountID).setNotificationsMarker(id, true);
|
||||||
realUnreadMarker=id;
|
realUnreadMarker=id;
|
||||||
|
updateMarkAllReadButton();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,6 +348,10 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateMarkAllReadButton(){
|
||||||
|
markAllReadItem.setEnabled(!data.isEmpty() && realUnreadMarker!=null && !realUnreadMarker.equals(data.get(0).id));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAppendItems(List<Notification> items){
|
public void onAppendItems(List<Notification> items){
|
||||||
super.onAppendItems(items);
|
super.onAppendItems(items);
|
||||||
@@ -348,20 +364,4 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean canRefreshWithoutUpsettingUser(){
|
|
||||||
// TODO maybe reload notifications the same way we reload the home timelines, i.e. with gaps and stuff
|
|
||||||
if(data.size()<=itemsPerPage)
|
|
||||||
return true;
|
|
||||||
for(int i=list.getChildCount()-1;i>=0;i--){
|
|
||||||
if(list.getChildViewHolder(list.getChildAt(i)) instanceof StatusDisplayItem.Holder<?> itemHolder){
|
|
||||||
String id=itemHolder.getItemID();
|
|
||||||
for(int j=0;j<data.size();j++){
|
|
||||||
if(data.get(j).id.equals(id))
|
|
||||||
return j<itemsPerPage; // Can refresh the list without losing scroll position if it is within the first page
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -301,7 +301,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
username+="@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
|
username+="@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
|
||||||
}
|
}
|
||||||
getActivity().getSystemService(ClipboardManager.class).setPrimaryClip(ClipData.newPlainText(null, "@"+username));
|
getActivity().getSystemService(ClipboardManager.class).setPrimaryClip(ClipData.newPlainText(null, "@"+username));
|
||||||
UiUtils.maybeShowTextCopiedToast(getActivity());
|
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.TIRAMISU || UiUtils.isMIUI()){ // Android 13+ SystemUI shows its own thing when you put things into the clipboard
|
||||||
|
Toast.makeText(getActivity(), R.string.text_copied, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -607,7 +609,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
menu.findItem(R.id.block_domain).setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
|
menu.findItem(R.id.block_domain).setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
|
||||||
else
|
else
|
||||||
menu.findItem(R.id.block_domain).setVisible(false);
|
menu.findItem(R.id.block_domain).setVisible(false);
|
||||||
menu.findItem(R.id.add_to_list).setVisible(relationship.following);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -661,11 +662,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
}else if(id==R.id.save){
|
}else if(id==R.id.save){
|
||||||
if(isInEditMode)
|
if(isInEditMode)
|
||||||
saveAndExitEditMode();
|
saveAndExitEditMode();
|
||||||
}else if(id==R.id.add_to_list){
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
args.putParcelable("targetAccount", Parcels.wrap(account));
|
|
||||||
Nav.go(getActivity(), AddAccountToListsFragment.class, args);
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -854,7 +850,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
aboutFragment.enterEditMode(account.source.fields);
|
aboutFragment.enterEditMode(account.source.fields);
|
||||||
refreshLayout.setEnabled(false);
|
refreshLayout.setEnabled(false);
|
||||||
editDirty=false;
|
editDirty=false;
|
||||||
V.setVisibilityAnimated(fab, View.GONE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void exitEditMode(){
|
private void exitEditMode(){
|
||||||
@@ -897,7 +892,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
refreshLayout.setEnabled(true);
|
refreshLayout.setEnabled(true);
|
||||||
|
|
||||||
bindHeaderView();
|
bindHeaderView();
|
||||||
V.setVisibilityAnimated(fab, View.VISIBLE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveAndExitEditMode(){
|
private void saveAndExitEditMode(){
|
||||||
@@ -1050,7 +1044,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
FrameLayout view=new FrameLayout(parent.getContext());
|
FrameLayout view=tabViews[viewType];
|
||||||
|
((ViewGroup)view.getParent()).removeView(view);
|
||||||
|
view.setVisibility(View.VISIBLE);
|
||||||
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
return new SimpleViewHolder(view);
|
return new SimpleViewHolder(view);
|
||||||
}
|
}
|
||||||
@@ -1058,13 +1054,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
|
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
|
||||||
Fragment fragment=getFragmentForPage(position);
|
Fragment fragment=getFragmentForPage(position);
|
||||||
FrameLayout fragmentView=tabViews[position];
|
|
||||||
fragmentView.setVisibility(View.VISIBLE);
|
|
||||||
if(fragmentView.getParent() instanceof ViewGroup parent)
|
|
||||||
parent.removeView(fragmentView);
|
|
||||||
((FrameLayout)holder.itemView).addView(fragmentView);
|
|
||||||
if(!fragment.isAdded()){
|
if(!fragment.isAdded()){
|
||||||
getChildFragmentManager().beginTransaction().add(fragmentView.getId(), fragment).commit();
|
getChildFragmentManager().beginTransaction().add(holder.itemView.getId(), fragment).commit();
|
||||||
holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreDraw(){
|
public boolean onPreDraw(){
|
||||||
|
|||||||
@@ -47,12 +47,13 @@ public class SplashFragment extends AppKitFragment{
|
|||||||
private ProgressBarButton defaultServerButton;
|
private ProgressBarButton defaultServerButton;
|
||||||
private ProgressBar defaultServerProgress;
|
private ProgressBar defaultServerProgress;
|
||||||
private String chosenDefaultServer=DEFAULT_SERVER;
|
private String chosenDefaultServer=DEFAULT_SERVER;
|
||||||
private boolean loadingDefaultServer, loadedDefaultServer;
|
private boolean loadingDefaultServer;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
motionEffect=new InterpolatingMotionEffect(MastodonApp.context);
|
motionEffect=new InterpolatingMotionEffect(MastodonApp.context);
|
||||||
|
loadAndChooseDefaultServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -100,8 +101,6 @@ public class SplashFragment extends AppKitFragment{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if(!loadedDefaultServer && !loadingDefaultServer)
|
|
||||||
loadAndChooseDefaultServer();
|
|
||||||
|
|
||||||
return contentView;
|
return contentView;
|
||||||
}
|
}
|
||||||
@@ -199,8 +198,6 @@ public class SplashFragment extends AppKitFragment{
|
|||||||
|
|
||||||
private void loadAndChooseDefaultServer(){
|
private void loadAndChooseDefaultServer(){
|
||||||
loadingDefaultServer=true;
|
loadingDefaultServer=true;
|
||||||
defaultServerButton.setTextVisible(false);
|
|
||||||
defaultServerProgress.setVisibility(View.VISIBLE);
|
|
||||||
new GetCatalogDefaultInstances()
|
new GetCatalogDefaultInstances()
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
@@ -242,7 +239,6 @@ public class SplashFragment extends AppKitFragment{
|
|||||||
private void setChosenDefaultServer(String domain){
|
private void setChosenDefaultServer(String domain){
|
||||||
chosenDefaultServer=domain;
|
chosenDefaultServer=domain;
|
||||||
loadingDefaultServer=false;
|
loadingDefaultServer=false;
|
||||||
loadedDefaultServer=true;
|
|
||||||
if(defaultServerButton!=null && getActivity()!=null){
|
if(defaultServerButton!=null && getActivity()!=null){
|
||||||
defaultServerButton.setTextVisible(true);
|
defaultServerButton.setTextVisible(true);
|
||||||
defaultServerProgress.setVisibility(View.GONE);
|
defaultServerProgress.setVisibility(View.GONE);
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
package org.joinmastodon.android.fragments.account_list;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.accounts.SearchAccounts;
|
|
||||||
import org.joinmastodon.android.model.Account;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
|
||||||
|
|
||||||
public class AddListMembersFragment extends AccountSearchFragment{
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState){
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
dataLoaded();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doLoadData(int offset, int count){
|
|
||||||
refreshing=true;
|
|
||||||
currentRequest=new SearchAccounts(currentQuery, 0, 0, false, true)
|
|
||||||
.setCallback(new SimpleCallback<>(this){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(List<Account> result){
|
|
||||||
AddListMembersFragment.this.onSuccess(result);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec(accountID);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getSearchViewPlaceholder(){
|
|
||||||
return getString(R.string.search_among_people_you_follow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
package org.joinmastodon.android.fragments.account_list;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.widget.Button;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountFollowing;
|
|
||||||
import org.joinmastodon.android.api.requests.accounts.SearchAccounts;
|
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
|
||||||
import org.joinmastodon.android.model.Account;
|
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
|
||||||
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
|
|
||||||
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
|
||||||
import me.grishka.appkit.utils.V;
|
|
||||||
|
|
||||||
@SuppressLint("ValidFragment") // This shouldn't be part of any saved states anyway
|
|
||||||
public class AddNewListMembersFragment extends AccountSearchFragment{
|
|
||||||
private Listener listener;
|
|
||||||
private String maxID;
|
|
||||||
|
|
||||||
public AddNewListMembersFragment(Listener listener){
|
|
||||||
this.listener=listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState){
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
loadData();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doLoadData(int offset, int count){
|
|
||||||
if(TextUtils.isEmpty(currentQuery)){
|
|
||||||
currentRequest=new GetAccountFollowing(AccountSessionManager.get(accountID).self.id, offset>0 ? maxID : null, count)
|
|
||||||
.setCallback(new SimpleCallback<>(this){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(HeaderPaginationList<Account> result){
|
|
||||||
setEmptyText("");
|
|
||||||
onDataLoaded(result.stream().map(a->new AccountViewModel(a, accountID)).collect(Collectors.toList()), result.nextPageUri!=null);
|
|
||||||
maxID=result.getNextPageMaxID();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec(accountID);
|
|
||||||
}else{
|
|
||||||
refreshing=true;
|
|
||||||
currentRequest=new SearchAccounts(currentQuery, 0, 0, false, true)
|
|
||||||
.setCallback(new SimpleCallback<>(this){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(List<Account> result){
|
|
||||||
AddNewListMembersFragment.this.onSuccess(result);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec(accountID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getSearchViewPlaceholder(){
|
|
||||||
return getString(R.string.search_among_people_you_follow);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onConfigureViewHolder(AccountViewHolder holder){
|
|
||||||
holder.setStyle(AccountViewHolder.AccessoryType.CUSTOM_BUTTON, false);
|
|
||||||
holder.setOnLongClickListener(vh->false);
|
|
||||||
Button button=holder.getButton();
|
|
||||||
button.setPadding(V.dp(24), 0, V.dp(24), 0);
|
|
||||||
button.setMinimumWidth(0);
|
|
||||||
button.setMinWidth(0);
|
|
||||||
button.setOnClickListener(v->{
|
|
||||||
holder.setActionProgressVisible(true);
|
|
||||||
holder.itemView.setHasTransientState(true);
|
|
||||||
Runnable onDone=()->{
|
|
||||||
holder.setActionProgressVisible(false);
|
|
||||||
holder.itemView.setHasTransientState(false);
|
|
||||||
onBindViewHolder(holder);
|
|
||||||
};
|
|
||||||
AccountViewModel account=holder.getItem();
|
|
||||||
if(listener.isAccountInList(account)){
|
|
||||||
listener.removeAccountAccountFromList(account, onDone);
|
|
||||||
}else{
|
|
||||||
listener.addAccountToList(account, onDone);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onBindViewHolder(AccountViewHolder holder){
|
|
||||||
Button button=holder.getButton();
|
|
||||||
int textRes, styleRes;
|
|
||||||
if(listener.isAccountInList(holder.getItem())){
|
|
||||||
textRes=R.string.remove;
|
|
||||||
styleRes=R.style.Widget_Mastodon_M3_Button_Tonal_Error;
|
|
||||||
}else{
|
|
||||||
textRes=R.string.add;
|
|
||||||
styleRes=R.style.Widget_Mastodon_M3_Button_Filled;
|
|
||||||
}
|
|
||||||
button.setText(textRes);
|
|
||||||
TypedArray ta=button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.background});
|
|
||||||
button.setBackground(ta.getDrawable(0));
|
|
||||||
ta.recycle();
|
|
||||||
ta=button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.textColor});
|
|
||||||
button.setTextColor(ta.getColorStateList(0));
|
|
||||||
ta.recycle();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void loadRelationships(List<AccountViewModel> accounts){
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Listener{
|
|
||||||
boolean isAccountInList(AccountViewModel account);
|
|
||||||
void addAccountToList(AccountViewModel account, Runnable onDone);
|
|
||||||
void removeAccountAccountFromList(AccountViewModel account, Runnable onDone);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -73,8 +73,6 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment<A
|
|||||||
|
|
||||||
protected void loadRelationships(List<AccountViewModel> accounts){
|
protected void loadRelationships(List<AccountViewModel> accounts){
|
||||||
Set<String> ids=accounts.stream().map(ai->ai.account.id).collect(Collectors.toSet());
|
Set<String> ids=accounts.stream().map(ai->ai.account.id).collect(Collectors.toSet());
|
||||||
if(ids.isEmpty())
|
|
||||||
return;
|
|
||||||
GetAccountRelationships req=new GetAccountRelationships(ids);
|
GetAccountRelationships req=new GetAccountRelationships(ids);
|
||||||
relationshipsRequests.add(req);
|
relationshipsRequests.add(req);
|
||||||
req.setCallback(new Callback<>(){
|
req.setCallback(new Callback<>(){
|
||||||
@@ -124,9 +122,20 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment<A
|
|||||||
Toolbar toolbar=getToolbar();
|
Toolbar toolbar=getToolbar();
|
||||||
if(toolbar!=null && toolbar.getNavigationIcon()!=null){
|
if(toolbar!=null && toolbar.getNavigationIcon()!=null){
|
||||||
toolbar.setNavigationContentDescription(R.string.back);
|
toolbar.setNavigationContentDescription(R.string.back);
|
||||||
|
if(hasSubtitle()){
|
||||||
|
toolbar.setTitleTextAppearance(getActivity(), R.style.m3_title_medium);
|
||||||
|
toolbar.setSubtitleTextAppearance(getActivity(), R.style.m3_body_medium);
|
||||||
|
int color=UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary);
|
||||||
|
toolbar.setTitleTextColor(color);
|
||||||
|
toolbar.setSubtitleTextColor(color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean hasSubtitle(){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onApplyWindowInsets(WindowInsets insets){
|
public void onApplyWindowInsets(WindowInsets insets){
|
||||||
if(Build.VERSION.SDK_INT>=29 && insets.getTappableElementInsets().bottom==0){
|
if(Build.VERSION.SDK_INT>=29 && insets.getTappableElementInsets().bottom==0){
|
||||||
@@ -141,7 +150,6 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment<A
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void onConfigureViewHolder(AccountViewHolder holder){}
|
protected void onConfigureViewHolder(AccountViewHolder holder){}
|
||||||
protected void onBindViewHolder(AccountViewHolder holder){}
|
|
||||||
|
|
||||||
protected class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
protected class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||||
public AccountsAdapter(){
|
public AccountsAdapter(){
|
||||||
@@ -159,7 +167,6 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment<A
|
|||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(AccountViewHolder holder, int position){
|
public void onBindViewHolder(AccountViewHolder holder, int position){
|
||||||
holder.bind(data.get(position));
|
holder.bind(data.get(position));
|
||||||
BaseAccountListFragment.this.onBindViewHolder(holder);
|
|
||||||
super.onBindViewHolder(holder, position);
|
super.onBindViewHolder(holder, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ import android.text.TextUtils;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
||||||
import org.joinmastodon.android.model.Account;
|
|
||||||
import org.joinmastodon.android.model.SearchResults;
|
import org.joinmastodon.android.model.SearchResults;
|
||||||
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
|
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
|
||||||
import org.joinmastodon.android.ui.SearchViewHelper;
|
import org.joinmastodon.android.ui.SearchViewHelper;
|
||||||
@@ -15,14 +13,13 @@ import org.joinmastodon.android.ui.utils.UiUtils;
|
|||||||
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
|
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
|
||||||
public class AccountSearchFragment extends BaseAccountListFragment{
|
public class ComposeAccountSearchFragment extends BaseAccountListFragment{
|
||||||
protected String currentQuery;
|
private String currentQuery;
|
||||||
private boolean resultDelivered;
|
private boolean resultDelivered;
|
||||||
private SearchViewHelper searchViewHelper;
|
private SearchViewHelper searchViewHelper;
|
||||||
|
|
||||||
@@ -31,11 +28,12 @@ public class AccountSearchFragment extends BaseAccountListFragment{
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setRefreshEnabled(false);
|
setRefreshEnabled(false);
|
||||||
setEmptyText("");
|
setEmptyText("");
|
||||||
|
dataLoaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
searchViewHelper=new SearchViewHelper(getActivity(), getToolbarContext(), getSearchViewPlaceholder());
|
searchViewHelper=new SearchViewHelper(getActivity(), getToolbarContext(), getString(R.string.search_hint));
|
||||||
searchViewHelper.setListeners(this::onQueryChanged, null);
|
searchViewHelper.setListeners(this::onQueryChanged, null);
|
||||||
searchViewHelper.addDivider(contentView);
|
searchViewHelper.addDivider(contentView);
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
@@ -53,21 +51,13 @@ public class AccountSearchFragment extends BaseAccountListFragment{
|
|||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(SearchResults result){
|
public void onSuccess(SearchResults result){
|
||||||
AccountSearchFragment.this.onSuccess(result.accounts);
|
setEmptyText(R.string.no_search_results);
|
||||||
|
onDataLoaded(result.accounts.stream().map(a->new AccountViewModel(a, accountID)).collect(Collectors.toList()), false);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onSuccess(List<Account> result){
|
|
||||||
setEmptyText(R.string.no_search_results);
|
|
||||||
onDataLoaded(result.stream().map(a->new AccountViewModel(a, accountID)).collect(Collectors.toList()), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getSearchViewPlaceholder(){
|
|
||||||
return getString(R.string.search_hint);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onUpdateToolbar(){
|
protected void onUpdateToolbar(){
|
||||||
super.onUpdateToolbar();
|
super.onUpdateToolbar();
|
||||||
@@ -14,4 +14,8 @@ public abstract class StatusRelatedAccountListFragment extends PaginatedAccountL
|
|||||||
status=Parcels.unwrap(getArguments().getParcelable("status"));
|
status=Parcels.unwrap(getArguments().getParcelable("status"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean hasSubtitle(){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
private DiscoverNewsFragment newsFragment;
|
private DiscoverNewsFragment newsFragment;
|
||||||
private DiscoverAccountsFragment accountsFragment;
|
private DiscoverAccountsFragment accountsFragment;
|
||||||
private SearchFragment searchFragment;
|
private SearchFragment searchFragment;
|
||||||
|
private LocalTimelineFragment localTimelineFragment;
|
||||||
|
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private String currentQuery;
|
private String currentQuery;
|
||||||
@@ -70,14 +71,15 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
tabLayout=view.findViewById(R.id.tabbar);
|
tabLayout=view.findViewById(R.id.tabbar);
|
||||||
pager=view.findViewById(R.id.pager);
|
pager=view.findViewById(R.id.pager);
|
||||||
|
|
||||||
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.discover_posts;
|
case 0 -> R.id.discover_posts;
|
||||||
case 1 -> R.id.discover_hashtags;
|
case 1 -> R.id.discover_hashtags;
|
||||||
case 2 -> R.id.discover_news;
|
case 2 -> R.id.discover_news;
|
||||||
case 3 -> R.id.discover_users;
|
case 3 -> R.id.discover_local_timeline;
|
||||||
|
case 4 -> R.id.discover_users;
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+i);
|
default -> throw new IllegalStateException("Unexpected value: "+i);
|
||||||
});
|
});
|
||||||
tabView.setVisibility(View.GONE);
|
tabView.setVisibility(View.GONE);
|
||||||
@@ -120,8 +122,12 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
accountsFragment=new DiscoverAccountsFragment();
|
accountsFragment=new DiscoverAccountsFragment();
|
||||||
accountsFragment.setArguments(args);
|
accountsFragment.setArguments(args);
|
||||||
|
|
||||||
|
localTimelineFragment=new LocalTimelineFragment();
|
||||||
|
localTimelineFragment.setArguments(args);
|
||||||
|
|
||||||
getChildFragmentManager().beginTransaction()
|
getChildFragmentManager().beginTransaction()
|
||||||
.add(R.id.discover_posts, postsFragment)
|
.add(R.id.discover_posts, postsFragment)
|
||||||
|
.add(R.id.discover_local_timeline, localTimelineFragment)
|
||||||
.add(R.id.discover_hashtags, hashtagsFragment)
|
.add(R.id.discover_hashtags, hashtagsFragment)
|
||||||
.add(R.id.discover_news, newsFragment)
|
.add(R.id.discover_news, newsFragment)
|
||||||
.add(R.id.discover_users, accountsFragment)
|
.add(R.id.discover_users, accountsFragment)
|
||||||
@@ -135,7 +141,8 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
case 0 -> R.string.posts;
|
case 0 -> R.string.posts;
|
||||||
case 1 -> R.string.hashtags;
|
case 1 -> R.string.hashtags;
|
||||||
case 2 -> R.string.news;
|
case 2 -> R.string.news;
|
||||||
case 3 -> R.string.for_you;
|
case 3 -> R.string.local_timeline;
|
||||||
|
case 4 -> R.string.for_you;
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+position);
|
default -> throw new IllegalStateException("Unexpected value: "+position);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -238,7 +245,8 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
case 0 -> postsFragment;
|
case 0 -> postsFragment;
|
||||||
case 1 -> hashtagsFragment;
|
case 1 -> hashtagsFragment;
|
||||||
case 2 -> newsFragment;
|
case 2 -> newsFragment;
|
||||||
case 3 -> accountsFragment;
|
case 3 -> localTimelineFragment;
|
||||||
|
case 4 -> accountsFragment;
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
default -> throw new IllegalStateException("Unexpected value: "+page);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -272,19 +280,15 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
FrameLayout view=new FrameLayout(parent.getContext());
|
FrameLayout view=tabViews[viewType];
|
||||||
|
((ViewGroup)view.getParent()).removeView(view);
|
||||||
|
view.setVisibility(View.VISIBLE);
|
||||||
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
return new SimpleViewHolder(view);
|
return new SimpleViewHolder(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
|
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){}
|
||||||
FrameLayout view=tabViews[position];
|
|
||||||
if(view.getParent() instanceof ViewGroup parent)
|
|
||||||
parent.removeView(view);
|
|
||||||
view.setVisibility(View.VISIBLE);
|
|
||||||
((FrameLayout)holder.itemView).addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount(){
|
public int getItemCount(){
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ package org.joinmastodon.android.fragments.discover;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.requests.trends.GetTrendingStatuses;
|
import org.joinmastodon.android.api.requests.trends.GetTrendingStatuses;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
|
||||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||||
import org.joinmastodon.android.model.FilterContext;
|
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||||
|
|
||||||
@@ -17,7 +15,6 @@ import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
|||||||
|
|
||||||
public class DiscoverPostsFragment extends StatusListFragment{
|
public class DiscoverPostsFragment extends StatusListFragment{
|
||||||
private DiscoverInfoBannerHelper bannerHelper;
|
private DiscoverInfoBannerHelper bannerHelper;
|
||||||
private int realOffset=0;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
@@ -27,12 +24,10 @@ public class DiscoverPostsFragment extends StatusListFragment{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
currentRequest=new GetTrendingStatuses(offset==0 ? 0 : realOffset, count)
|
currentRequest=new GetTrendingStatuses(offset, count)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
realOffset+=result.size();
|
|
||||||
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.PUBLIC);
|
|
||||||
onDataLoaded(result, !result.isEmpty());
|
onDataLoaded(result, !result.isEmpty());
|
||||||
bannerHelper.onBannerBecameVisible();
|
bannerHelper.onBannerBecameVisible();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package org.joinmastodon.android.fragments.discover;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||||
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||||
|
|
||||||
|
public class LocalTimelineFragment extends StatusListFragment{
|
||||||
|
private DiscoverInfoBannerHelper bannerHelper;
|
||||||
|
|
||||||
|
private String maxID;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState){
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.LOCAL_TIMELINE, accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doLoadData(int offset, int count){
|
||||||
|
currentRequest=new GetPublicTimeline(true, false, refreshing ? null : maxID, count)
|
||||||
|
.setCallback(new SimpleCallback<>(this){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<Status> result){
|
||||||
|
if(!result.isEmpty())
|
||||||
|
maxID=result.get(result.size()-1).id;
|
||||||
|
boolean empty=result.isEmpty();
|
||||||
|
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.PUBLIC);
|
||||||
|
onDataLoaded(result, !empty);
|
||||||
|
bannerHelper.onBannerBecameVisible();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RecyclerView.Adapter getAdapter(){
|
||||||
|
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
|
||||||
|
bannerHelper.maybeAddBanner(list, adapter);
|
||||||
|
adapter.addAdapter(super.getAdapter());
|
||||||
|
return adapter;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,7 +32,6 @@ import java.util.stream.Collectors;
|
|||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
|
||||||
|
|
||||||
public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
||||||
private String currentQuery;
|
private String currentQuery;
|
||||||
@@ -138,7 +137,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
|||||||
}*/
|
}*/
|
||||||
int offset=_offset;
|
int offset=_offset;
|
||||||
currentRequest=new GetSearchResults(currentQuery, type, type==null, maxID, offset, type==null ? 0 : count)
|
currentRequest=new GetSearchResults(currentQuery, type, type==null, maxID, offset, type==null ? 0 : count)
|
||||||
.setCallback(new SimpleCallback<SearchResults>(this){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(SearchResults result){
|
public void onSuccess(SearchResults result){
|
||||||
ArrayList<SearchResult> results=new ArrayList<>();
|
ArrayList<SearchResult> results=new ArrayList<>();
|
||||||
@@ -159,10 +158,16 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
|||||||
}
|
}
|
||||||
prevDisplayItems=new ArrayList<>(displayItems);
|
prevDisplayItems=new ArrayList<>(displayItems);
|
||||||
unfilteredResults=results;
|
unfilteredResults=results;
|
||||||
boolean wasRefreshing=refreshing;
|
|
||||||
onDataLoaded(filterSearchResults(results), type!=null && !results.isEmpty());
|
onDataLoaded(filterSearchResults(results), type!=null && !results.isEmpty());
|
||||||
if(wasRefreshing)
|
}
|
||||||
list.scrollToPosition(0);
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
currentRequest=null;
|
||||||
|
Activity a=getActivity();
|
||||||
|
if(a==null)
|
||||||
|
return;
|
||||||
|
error.showToast(a);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
|
|||||||
onDataLoaded(results.stream().map(sr->{
|
onDataLoaded(results.stream().map(sr->{
|
||||||
SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, true);
|
SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, true);
|
||||||
if(sr.type==SearchResult.Type.HASHTAG){
|
if(sr.type==SearchResult.Type.HASHTAG){
|
||||||
vm.hashtagItem.setOnClick(i->openHashtag(sr));
|
vm.hashtagItem.onClick=()->openHashtag(sr);
|
||||||
}
|
}
|
||||||
return vm;
|
return vm;
|
||||||
}).collect(Collectors.toList()), false);
|
}).collect(Collectors.toList()), false);
|
||||||
@@ -129,7 +129,7 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
|
|||||||
.map(sr->{
|
.map(sr->{
|
||||||
SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, false);
|
SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, false);
|
||||||
if(sr.type==SearchResult.Type.HASHTAG){
|
if(sr.type==SearchResult.Type.HASHTAG){
|
||||||
vm.hashtagItem.setOnClick(i->openHashtag(sr));
|
vm.hashtagItem.onClick=()->openHashtag(sr);
|
||||||
}
|
}
|
||||||
return vm;
|
return vm;
|
||||||
})
|
})
|
||||||
@@ -384,23 +384,21 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onSearchViewEnter(){
|
private void onSearchViewEnter(){
|
||||||
if(TextUtils.isEmpty(currentQuery) || currentQuery.trim().isEmpty())
|
|
||||||
return;
|
|
||||||
deliverResult(currentQuery, null);
|
deliverResult(currentQuery, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onOpenURLClick(ListItem<?> item_){
|
private void onOpenURLClick(){
|
||||||
((MainActivity)getActivity()).handleURL(Uri.parse(searchViewHelper.getQuery()), accountID);
|
((MainActivity)getActivity()).handleURL(Uri.parse(searchViewHelper.getQuery()), accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onGoToHashtagClick(ListItem<?> item_){
|
private void onGoToHashtagClick(){
|
||||||
String q=searchViewHelper.getQuery();
|
String q=searchViewHelper.getQuery();
|
||||||
if(q.startsWith("#"))
|
if(q.startsWith("#"))
|
||||||
q=q.substring(1);
|
q=q.substring(1);
|
||||||
UiUtils.openHashtagTimeline(getActivity(), accountID, q);
|
UiUtils.openHashtagTimeline(getActivity(), accountID, q);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onGoToAccountClick(ListItem<?> item_){
|
private void onGoToAccountClick(){
|
||||||
String q=searchViewHelper.getQuery();
|
String q=searchViewHelper.getQuery();
|
||||||
if(!q.startsWith("@")){
|
if(!q.startsWith("@")){
|
||||||
q="@"+q;
|
q="@"+q;
|
||||||
@@ -411,11 +409,11 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
|
|||||||
((MainActivity)getActivity()).openSearchQuery(q, accountID, R.string.loading, true);
|
((MainActivity)getActivity()).openSearchQuery(q, accountID, R.string.loading, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onGoToStatusSearchClick(ListItem<?> item_){
|
private void onGoToStatusSearchClick(){
|
||||||
deliverResult(searchViewHelper.getQuery(), SearchResult.Type.STATUS);
|
deliverResult(searchViewHelper.getQuery(), SearchResult.Type.STATUS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onGoToAccountSearchClick(ListItem<?> item_){
|
private void onGoToAccountSearchClick(){
|
||||||
deliverResult(searchViewHelper.getQuery(), SearchResult.Type.ACCOUNT);
|
deliverResult(searchViewHelper.getQuery(), SearchResult.Type.ACCOUNT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -165,7 +165,9 @@ public class AccountActivationFragment extends ToolbarFragment{
|
|||||||
private void tryGetAccount(){
|
private void tryGetAccount(){
|
||||||
if(AccountSessionManager.getInstance().tryGetAccount(accountID)==null){
|
if(AccountSessionManager.getInstance().tryGetAccount(accountID)==null){
|
||||||
uiHandler.removeCallbacks(pollRunnable);
|
uiHandler.removeCallbacks(pollRunnable);
|
||||||
((MainActivity)getActivity()).restartHomeFragment();
|
getActivity().finish();
|
||||||
|
Intent intent=new Intent(getActivity(), MainActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
currentRequest=new GetOwnAccount()
|
currentRequest=new GetOwnAccount()
|
||||||
|
|||||||
@@ -12,10 +12,12 @@ import org.joinmastodon.android.model.viewmodel.ListItem;
|
|||||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
import org.joinmastodon.android.ui.adapters.GenericListItemsAdapter;
|
import org.joinmastodon.android.ui.adapters.GenericListItemsAdapter;
|
||||||
|
import org.joinmastodon.android.ui.viewholders.CheckableListItemViewHolder;
|
||||||
import org.joinmastodon.android.ui.viewholders.ListItemViewHolder;
|
import org.joinmastodon.android.ui.viewholders.ListItemViewHolder;
|
||||||
import org.joinmastodon.android.ui.viewholders.SimpleListItemViewHolder;
|
import org.joinmastodon.android.ui.viewholders.SimpleListItemViewHolder;
|
||||||
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public abstract class BaseSettingsFragment<T> extends MastodonRecyclerFragment<ListItem<T>>{
|
public abstract class BaseSettingsFragment<T> extends MastodonRecyclerFragment<ListItem<T>>{
|
||||||
protected GenericListItemsAdapter<T> itemsAdapter;
|
protected GenericListItemsAdapter<T> itemsAdapter;
|
||||||
@@ -43,7 +45,7 @@ public abstract class BaseSettingsFragment<T> extends MastodonRecyclerFragment<L
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RecyclerView.Adapter<?> getAdapter(){
|
protected RecyclerView.Adapter<?> getAdapter(){
|
||||||
return itemsAdapter=new GenericListItemsAdapter<T>(imgLoader, data);
|
return itemsAdapter=new GenericListItemsAdapter<T>(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -57,13 +59,12 @@ public abstract class BaseSettingsFragment<T> extends MastodonRecyclerFragment<L
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void toggleCheckableItem(ListItem<?> item){
|
protected void toggleCheckableItem(CheckableListItem<T> item){
|
||||||
if(item instanceof CheckableListItem<?> checkable)
|
item.toggle();
|
||||||
checkable.toggle();
|
|
||||||
rebindItem(item);
|
rebindItem(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void rebindItem(ListItem<?> item){
|
protected void rebindItem(ListItem<T> item){
|
||||||
if(list==null)
|
if(list==null)
|
||||||
return;
|
return;
|
||||||
if(list.findViewHolderForAdapterPosition(indexOfItemsAdapter()+data.indexOf(item)) instanceof ListItemViewHolder<?> holder){
|
if(list.findViewHolderForAdapterPosition(indexOfItemsAdapter()+data.indexOf(item)) instanceof ListItemViewHolder<?> holder){
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ public class EditFilterFragment extends BaseSettingsFragment<Void> implements On
|
|||||||
durationItem=new ListItem<>(R.string.settings_filter_duration, 0, this::onDurationClick),
|
durationItem=new ListItem<>(R.string.settings_filter_duration, 0, this::onDurationClick),
|
||||||
wordsItem=new ListItem<>(R.string.settings_filter_muted_words, 0, this::onWordsClick),
|
wordsItem=new ListItem<>(R.string.settings_filter_muted_words, 0, this::onWordsClick),
|
||||||
contextItem=new ListItem<>(R.string.settings_filter_context, 0, this::onContextClick),
|
contextItem=new ListItem<>(R.string.settings_filter_context, 0, this::onContextClick),
|
||||||
cwItem=new CheckableListItem<>(R.string.settings_filter_show_cw, R.string.settings_filter_show_cw_explanation, CheckableListItem.Style.SWITCH, filter==null || filter.filterAction==FilterAction.WARN, this::toggleCheckableItem)
|
cwItem=new CheckableListItem<>(R.string.settings_filter_show_cw, R.string.settings_filter_show_cw_explanation, CheckableListItem.Style.SWITCH, filter==null || filter.filterAction==FilterAction.WARN, ()->toggleCheckableItem(cwItem))
|
||||||
));
|
));
|
||||||
|
|
||||||
if(filter!=null){
|
if(filter!=null){
|
||||||
@@ -113,7 +113,7 @@ public class EditFilterFragment extends BaseSettingsFragment<Void> implements On
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDurationClick(ListItem<Void> item_){
|
private void onDurationClick(){
|
||||||
int[] durationOptions={
|
int[] durationOptions={
|
||||||
1800,
|
1800,
|
||||||
3600,
|
3600,
|
||||||
@@ -182,21 +182,21 @@ public class EditFilterFragment extends BaseSettingsFragment<Void> implements On
|
|||||||
alert.setOnDismissListener(dialog->callback.accept(null));
|
alert.setOnDismissListener(dialog->callback.accept(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onWordsClick(ListItem<Void> item){
|
private void onWordsClick(){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putParcelableArrayList("words", (ArrayList<? extends Parcelable>) keywords.stream().map(Parcels::wrap).collect(Collectors.toCollection(ArrayList::new)));
|
args.putParcelableArrayList("words", (ArrayList<? extends Parcelable>) keywords.stream().map(Parcels::wrap).collect(Collectors.toCollection(ArrayList::new)));
|
||||||
Nav.goForResult(getActivity(), FilterWordsFragment.class, args, WORDS_RESULT, this);
|
Nav.goForResult(getActivity(), FilterWordsFragment.class, args, WORDS_RESULT, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onContextClick(ListItem<Void> item){
|
private void onContextClick(){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putSerializable("context", context);
|
args.putSerializable("context", context);
|
||||||
Nav.goForResult(getActivity(), FilterContextFragment.class, args, CONTEXT_RESULT, this);
|
Nav.goForResult(getActivity(), FilterContextFragment.class, args, CONTEXT_RESULT, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDeleteClick(ListItem<Void> item_){
|
private void onDeleteClick(){
|
||||||
AlertDialog alert=new M3AlertDialogBuilder(getActivity())
|
AlertDialog alert=new M3AlertDialogBuilder(getActivity())
|
||||||
.setTitle(getString(R.string.settings_delete_filter_title, filter.title))
|
.setTitle(getString(R.string.settings_delete_filter_title, filter.title))
|
||||||
.setMessage(R.string.settings_delete_filter_confirmation)
|
.setMessage(R.string.settings_delete_filter_confirmation)
|
||||||
|
|||||||
@@ -22,9 +22,10 @@ public class FilterContextFragment extends BaseSettingsFragment<FilterContext> i
|
|||||||
setTitle(R.string.settings_filter_context);
|
setTitle(R.string.settings_filter_context);
|
||||||
context=(EnumSet<FilterContext>) getArguments().getSerializable("context");
|
context=(EnumSet<FilterContext>) getArguments().getSerializable("context");
|
||||||
onDataLoaded(Arrays.stream(FilterContext.values()).map(c->{
|
onDataLoaded(Arrays.stream(FilterContext.values()).map(c->{
|
||||||
CheckableListItem<FilterContext> item=new CheckableListItem<>(c.getDisplayNameRes(), 0, CheckableListItem.Style.CHECKBOX, context.contains(c), this::toggleCheckableItem);
|
CheckableListItem<FilterContext> item=new CheckableListItem<>(c.getDisplayNameRes(), 0, CheckableListItem.Style.CHECKBOX, context.contains(c), null);
|
||||||
item.parentObject=c;
|
item.parentObject=c;
|
||||||
item.isEnabled=true;
|
item.isEnabled=true;
|
||||||
|
item.onClick=()->toggleCheckableItem(item);
|
||||||
return item;
|
return item;
|
||||||
}).collect(Collectors.toList()));
|
}).collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
package org.joinmastodon.android.fragments.settings;
|
package org.joinmastodon.android.fragments.settings;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorListenerAdapter;
|
||||||
|
import android.animation.IntEvaluator;
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
@@ -22,7 +27,6 @@ import org.joinmastodon.android.model.FilterKeyword;
|
|||||||
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
|
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
|
||||||
import org.joinmastodon.android.model.viewmodel.ListItem;
|
import org.joinmastodon.android.model.viewmodel.ListItem;
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.utils.ActionModeHelper;
|
|
||||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.FloatingHintEditTextLayout;
|
import org.joinmastodon.android.ui.views.FloatingHintEditTextLayout;
|
||||||
@@ -33,6 +37,7 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import me.grishka.appkit.FragmentStackActivity;
|
||||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
@@ -55,7 +60,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
|
|||||||
FilterKeyword word=Parcels.unwrap(p);
|
FilterKeyword word=Parcels.unwrap(p);
|
||||||
ListItem<FilterKeyword> item=new ListItem<>(word.keyword, null, null, word);
|
ListItem<FilterKeyword> item=new ListItem<>(word.keyword, null, null, word);
|
||||||
item.isEnabled=true;
|
item.isEnabled=true;
|
||||||
item.setOnClick(this::onWordClick);
|
item.onClick=()->onWordClick(item);
|
||||||
return item;
|
return item;
|
||||||
}).collect(Collectors.toList()));
|
}).collect(Collectors.toList()));
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
@@ -109,7 +114,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||||
inflater.inflate(R.menu.selectable_list, menu);
|
inflater.inflate(R.menu.settings_filter_words, menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -169,7 +174,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
|
|||||||
w.keyword=input;
|
w.keyword=input;
|
||||||
ListItem<FilterKeyword> item=new ListItem<>(w.keyword, null, null, w);
|
ListItem<FilterKeyword> item=new ListItem<>(w.keyword, null, null, w);
|
||||||
item.isEnabled=true;
|
item.isEnabled=true;
|
||||||
item.setOnClick(this::onWordClick);
|
item.onClick=()->onWordClick(item);
|
||||||
data.add(item);
|
data.add(item);
|
||||||
itemsAdapter.notifyItemInserted(data.size()-1);
|
itemsAdapter.notifyItemInserted(data.size()-1);
|
||||||
}else{
|
}else{
|
||||||
@@ -223,15 +228,29 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
|
|||||||
return;
|
return;
|
||||||
V.setVisibilityAnimated(fab, View.GONE);
|
V.setVisibilityAnimated(fab, View.GONE);
|
||||||
|
|
||||||
actionMode=ActionModeHelper.startActionMode(this, ()->elevationOnScrollListener.getCurrentStatusBarColor(), new ActionMode.Callback(){
|
actionMode=getActivity().startActionMode(new ActionMode.Callback(){
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateActionMode(ActionMode mode, Menu menu){
|
public boolean onCreateActionMode(ActionMode mode, Menu menu){
|
||||||
|
ObjectAnimator anim=ObjectAnimator.ofInt(getActivity().getWindow(), "statusBarColor", elevationOnScrollListener.getCurrentStatusBarColor(), UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary));
|
||||||
|
anim.setEvaluator(new IntEvaluator(){
|
||||||
|
@Override
|
||||||
|
public Integer evaluate(float fraction, Integer startValue, Integer endValue){
|
||||||
|
return UiUtils.alphaBlendColors(startValue, endValue, fraction);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
anim.start();
|
||||||
|
((FragmentStackActivity) getActivity()).invalidateSystemBarColors(FilterWordsFragment.this);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu){
|
public boolean onPrepareActionMode(ActionMode mode, Menu menu){
|
||||||
mode.getMenuInflater().inflate(R.menu.settings_filter_words_action_mode, menu);
|
mode.getMenuInflater().inflate(R.menu.settings_filter_words_action_mode, menu);
|
||||||
|
for(int i=0;i<menu.size();i++){
|
||||||
|
Drawable icon=menu.getItem(i).getIcon().mutate();
|
||||||
|
icon.setTint(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnPrimary));
|
||||||
|
menu.getItem(i).setIcon(icon);
|
||||||
|
}
|
||||||
deleteItem=menu.findItem(R.id.delete);
|
deleteItem=menu.findItem(R.id.delete);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -247,6 +266,21 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
|
|||||||
@Override
|
@Override
|
||||||
public void onDestroyActionMode(ActionMode mode){
|
public void onDestroyActionMode(ActionMode mode){
|
||||||
leaveSelectionMode(true);
|
leaveSelectionMode(true);
|
||||||
|
ObjectAnimator anim=ObjectAnimator.ofInt(getActivity().getWindow(), "statusBarColor", UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary), elevationOnScrollListener.getCurrentStatusBarColor());
|
||||||
|
anim.setEvaluator(new IntEvaluator(){
|
||||||
|
@Override
|
||||||
|
public Integer evaluate(float fraction, Integer startValue, Integer endValue){
|
||||||
|
return UiUtils.alphaBlendColors(startValue, endValue, fraction);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
anim.addListener(new AnimatorListenerAdapter(){
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation){
|
||||||
|
getActivity().getWindow().setStatusBarColor(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
anim.start();
|
||||||
|
((FragmentStackActivity) getActivity()).invalidateSystemBarColors(FilterWordsFragment.this);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -255,7 +289,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
|
|||||||
ListItem<FilterKeyword> item=data.get(i);
|
ListItem<FilterKeyword> item=data.get(i);
|
||||||
CheckableListItem<FilterKeyword> newItem=new CheckableListItem<>(item.title, null, CheckableListItem.Style.CHECKBOX, selectAll, null);
|
CheckableListItem<FilterKeyword> newItem=new CheckableListItem<>(item.title, null, CheckableListItem.Style.CHECKBOX, selectAll, null);
|
||||||
newItem.isEnabled=true;
|
newItem.isEnabled=true;
|
||||||
newItem.setOnClick(this::onSelectionModeWordClick);
|
newItem.onClick=()->onSelectionModeWordClick(newItem);
|
||||||
newItem.parentObject=item.parentObject;
|
newItem.parentObject=item.parentObject;
|
||||||
if(selectAll)
|
if(selectAll)
|
||||||
selectedItems.add(newItem);
|
selectedItems.add(newItem);
|
||||||
@@ -279,7 +313,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
|
|||||||
ListItem<FilterKeyword> item=data.get(i);
|
ListItem<FilterKeyword> item=data.get(i);
|
||||||
ListItem<FilterKeyword> newItem=new ListItem<>(item.title, null, null);
|
ListItem<FilterKeyword> newItem=new ListItem<>(item.title, null, null);
|
||||||
newItem.isEnabled=true;
|
newItem.isEnabled=true;
|
||||||
newItem.setOnClick(this::onWordClick);
|
newItem.onClick=()->onWordClick(newItem);
|
||||||
newItem.parentObject=item.parentObject;
|
newItem.parentObject=item.parentObject;
|
||||||
data.set(i, newItem);
|
data.set(i, newItem);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void>{
|
|||||||
setTitle(getString(R.string.about_app, getString(R.string.app_name)));
|
setTitle(getString(R.string.about_app, getString(R.string.app_name)));
|
||||||
AccountSession s=AccountSessionManager.get(accountID);
|
AccountSession s=AccountSessionManager.get(accountID);
|
||||||
onDataLoaded(List.of(
|
onDataLoaded(List.of(
|
||||||
new ListItem<>(R.string.settings_even_more, 0, i->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/auth/edit")),
|
new ListItem<>(R.string.settings_even_more, 0, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/auth/edit")),
|
||||||
new ListItem<>(R.string.settings_contribute, 0, i->UiUtils.launchWebBrowser(getActivity(), getString(R.string.github_url))),
|
new ListItem<>(R.string.settings_contribute, 0, ()->UiUtils.launchWebBrowser(getActivity(), getString(R.string.github_url))),
|
||||||
new ListItem<>(R.string.settings_tos, 0, i->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/terms")),
|
new ListItem<>(R.string.settings_tos, 0, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/terms")),
|
||||||
new ListItem<>(R.string.settings_privacy_policy, 0, i->UiUtils.launchWebBrowser(getActivity(), getString(R.string.privacy_policy_url)), 0, true),
|
new ListItem<>(R.string.settings_privacy_policy, 0, ()->UiUtils.launchWebBrowser(getActivity(), getString(R.string.privacy_policy_url)), 0, true),
|
||||||
mediaCacheItem=new ListItem<>(R.string.settings_clear_cache, 0, this::onClearMediaCacheClick)
|
mediaCacheItem=new ListItem<>(R.string.settings_clear_cache, 0, this::onClearMediaCacheClick)
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void>{
|
|||||||
return adapter;
|
return adapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onClearMediaCacheClick(ListItem<?> item){
|
private void onClearMediaCacheClick(){
|
||||||
MastodonAPIController.runInBackground(()->{
|
MastodonAPIController.runInBackground(()->{
|
||||||
Activity activity=getActivity();
|
Activity activity=getActivity();
|
||||||
ImageCache.getInstance(getActivity()).clear();
|
ImageCache.getInstance(getActivity()).clear();
|
||||||
|
|||||||
@@ -33,19 +33,19 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void>{
|
|||||||
|
|
||||||
onDataLoaded(List.of(
|
onDataLoaded(List.of(
|
||||||
languageItem=new ListItem<>(getString(R.string.default_post_language), postLanguage!=null ? postLanguage.getDisplayName(Locale.getDefault()) : null, R.drawable.ic_language_24px, this::onDefaultLanguageClick),
|
languageItem=new ListItem<>(getString(R.string.default_post_language), postLanguage!=null ? postLanguage.getDisplayName(Locale.getDefault()) : null, R.drawable.ic_language_24px, this::onDefaultLanguageClick),
|
||||||
altTextItem=new CheckableListItem<>(R.string.settings_alt_text_reminders, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.altTextReminders, R.drawable.ic_alt_24px, this::toggleCheckableItem),
|
altTextItem=new CheckableListItem<>(R.string.settings_alt_text_reminders, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.altTextReminders, R.drawable.ic_alt_24px, ()->toggleCheckableItem(altTextItem)),
|
||||||
playGifsItem=new CheckableListItem<>(R.string.settings_gif, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.playGifs, R.drawable.ic_animation_24px, this::toggleCheckableItem),
|
playGifsItem=new CheckableListItem<>(R.string.settings_gif, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.playGifs, R.drawable.ic_animation_24px, ()->toggleCheckableItem(playGifsItem)),
|
||||||
customTabsItem=new CheckableListItem<>(R.string.settings_custom_tabs, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.useCustomTabs, R.drawable.ic_open_in_browser_24px, this::toggleCheckableItem),
|
customTabsItem=new CheckableListItem<>(R.string.settings_custom_tabs, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.useCustomTabs, R.drawable.ic_open_in_browser_24px, ()->toggleCheckableItem(customTabsItem)),
|
||||||
confirmUnfollowItem=new CheckableListItem<>(R.string.settings_confirm_unfollow, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmUnfollow, R.drawable.ic_person_remove_24px, this::toggleCheckableItem),
|
confirmUnfollowItem=new CheckableListItem<>(R.string.settings_confirm_unfollow, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmUnfollow, R.drawable.ic_person_remove_24px, ()->toggleCheckableItem(confirmUnfollowItem)),
|
||||||
confirmBoostItem=new CheckableListItem<>(R.string.settings_confirm_boost, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmBoost, R.drawable.ic_repeat_24px, this::toggleCheckableItem),
|
confirmBoostItem=new CheckableListItem<>(R.string.settings_confirm_boost, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmBoost, R.drawable.ic_repeat_24px, ()->toggleCheckableItem(confirmBoostItem)),
|
||||||
confirmDeleteItem=new CheckableListItem<>(R.string.settings_confirm_delete_post, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmDeletePost, R.drawable.ic_delete_24px, this::toggleCheckableItem)
|
confirmDeleteItem=new CheckableListItem<>(R.string.settings_confirm_delete_post, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmDeletePost, R.drawable.ic_delete_24px, ()->toggleCheckableItem(confirmDeleteItem))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){}
|
protected void doLoadData(int offset, int count){}
|
||||||
|
|
||||||
private void onDefaultLanguageClick(ListItem<?> item){
|
private void onDefaultLanguageClick(){
|
||||||
ComposeLanguageAlertViewController vc=new ComposeLanguageAlertViewController(getActivity(), null, new ComposeLanguageAlertViewController.SelectedOption(-1, postLanguage), null);
|
ComposeLanguageAlertViewController vc=new ComposeLanguageAlertViewController(getActivity(), null, new ComposeLanguageAlertViewController.SelectedOption(-1, postLanguage), null);
|
||||||
new M3AlertDialogBuilder(getActivity())
|
new M3AlertDialogBuilder(getActivity())
|
||||||
.setTitle(R.string.default_post_language)
|
.setTitle(R.string.default_post_language)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public class SettingsDebugFragment extends BaseSettingsFragment<Void>{
|
|||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){}
|
protected void doLoadData(int offset, int count){}
|
||||||
|
|
||||||
private void onTestEmailConfirmClick(ListItem<?> item){
|
private void onTestEmailConfirmClick(){
|
||||||
AccountSession sess=AccountSessionManager.getInstance().getAccount(accountID);
|
AccountSession sess=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
sess.activated=false;
|
sess.activated=false;
|
||||||
sess.activationInfo=new AccountActivationInfo("test@email", System.currentTimeMillis());
|
sess.activationInfo=new AccountActivationInfo("test@email", System.currentTimeMillis());
|
||||||
@@ -49,18 +49,18 @@ public class SettingsDebugFragment extends BaseSettingsFragment<Void>{
|
|||||||
Nav.goClearingStack(getActivity(), AccountActivationFragment.class, args);
|
Nav.goClearingStack(getActivity(), AccountActivationFragment.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onForceSelfUpdateClick(ListItem<?> item){
|
private void onForceSelfUpdateClick(){
|
||||||
GithubSelfUpdater.forceUpdate=true;
|
GithubSelfUpdater.forceUpdate=true;
|
||||||
GithubSelfUpdater.getInstance().maybeCheckForUpdates();
|
GithubSelfUpdater.getInstance().maybeCheckForUpdates();
|
||||||
restartUI();
|
restartUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onResetUpdaterClick(ListItem<?> item){
|
private void onResetUpdaterClick(){
|
||||||
GithubSelfUpdater.getInstance().reset();
|
GithubSelfUpdater.getInstance().reset();
|
||||||
restartUI();
|
restartUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onResetDiscoverBannersClick(ListItem<?> item){
|
private void onResetDiscoverBannersClick(){
|
||||||
DiscoverInfoBannerHelper.reset();
|
DiscoverInfoBannerHelper.reset();
|
||||||
restartUI();
|
restartUI();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,10 +39,10 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
|
|||||||
AccountLocalPreferences lp=s.getLocalPreferences();
|
AccountLocalPreferences lp=s.getLocalPreferences();
|
||||||
onDataLoaded(List.of(
|
onDataLoaded(List.of(
|
||||||
themeItem=new ListItem<>(R.string.settings_theme, getAppearanceValue(), R.drawable.ic_dark_mode_24px, this::onAppearanceClick),
|
themeItem=new ListItem<>(R.string.settings_theme, getAppearanceValue(), R.drawable.ic_dark_mode_24px, this::onAppearanceClick),
|
||||||
showCWsItem=new CheckableListItem<>(R.string.settings_show_cws, 0, CheckableListItem.Style.SWITCH, lp.showCWs, R.drawable.ic_warning_24px, this::toggleCheckableItem),
|
showCWsItem=new CheckableListItem<>(R.string.settings_show_cws, 0, CheckableListItem.Style.SWITCH, lp.showCWs, R.drawable.ic_warning_24px, ()->toggleCheckableItem(showCWsItem)),
|
||||||
hideSensitiveMediaItem=new CheckableListItem<>(R.string.settings_hide_sensitive_media, 0, CheckableListItem.Style.SWITCH, lp.hideSensitiveMedia, R.drawable.ic_no_adult_content_24px, this::toggleCheckableItem),
|
hideSensitiveMediaItem=new CheckableListItem<>(R.string.settings_hide_sensitive_media, 0, CheckableListItem.Style.SWITCH, lp.hideSensitiveMedia, R.drawable.ic_no_adult_content_24px, ()->toggleCheckableItem(hideSensitiveMediaItem)),
|
||||||
interactionCountsItem=new CheckableListItem<>(R.string.settings_show_interaction_counts, 0, CheckableListItem.Style.SWITCH, lp.showInteractionCounts, R.drawable.ic_social_leaderboard_24px, this::toggleCheckableItem),
|
interactionCountsItem=new CheckableListItem<>(R.string.settings_show_interaction_counts, 0, CheckableListItem.Style.SWITCH, lp.showInteractionCounts, R.drawable.ic_social_leaderboard_24px, ()->toggleCheckableItem(interactionCountsItem)),
|
||||||
emojiInNamesItem=new CheckableListItem<>(R.string.settings_show_emoji_in_names, 0, CheckableListItem.Style.SWITCH, lp.customEmojiInNames, R.drawable.ic_emoticon_24px, this::toggleCheckableItem)
|
emojiInNamesItem=new CheckableListItem<>(R.string.settings_show_emoji_in_names, 0, CheckableListItem.Style.SWITCH, lp.customEmojiInNames, R.drawable.ic_emoticon_24px, ()->toggleCheckableItem(emojiInNamesItem))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onAppearanceClick(ListItem<?> item_){
|
private void onAppearanceClick(){
|
||||||
int selected=switch(GlobalUserPreferences.theme){
|
int selected=switch(GlobalUserPreferences.theme){
|
||||||
case LIGHT -> 0;
|
case LIGHT -> 0;
|
||||||
case DARK -> 1;
|
case DARK -> 1;
|
||||||
|
|||||||
@@ -67,14 +67,16 @@ public class SettingsFiltersFragment extends BaseSettingsFragment<Filter>{
|
|||||||
Nav.go(getActivity(), EditFilterFragment.class, args);
|
Nav.go(getActivity(), EditFilterFragment.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onAddFilterClick(ListItem<?> item){
|
private void onAddFilterClick(){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
Nav.go(getActivity(), EditFilterFragment.class, args);
|
Nav.go(getActivity(), EditFilterFragment.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ListItem<Filter> makeListItem(Filter f){
|
private ListItem<Filter> makeListItem(Filter f){
|
||||||
ListItem<Filter> item=new ListItem<>(f.title, getString(f.isActive() ? R.string.filter_active : R.string.filter_inactive), this::onFilterClick, f);
|
ListItem<Filter> item=new ListItem<>(f.title, getString(f.isActive() ? R.string.filter_active : R.string.filter_inactive), null, f);
|
||||||
|
item.onClick=()->onFilterClick(item);
|
||||||
|
item.isEnabled=true;
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ 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.events.SelfUpdateStateChangedEvent;
|
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||||
import org.joinmastodon.android.model.viewmodel.ListItem;
|
import org.joinmastodon.android.model.viewmodel.ListItem;
|
||||||
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
|
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
@@ -58,12 +57,11 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
|
|||||||
new ListItem<>(R.string.settings_notifications, 0, R.drawable.ic_notifications_24px, this::onNotificationsClick),
|
new ListItem<>(R.string.settings_notifications, 0, R.drawable.ic_notifications_24px, this::onNotificationsClick),
|
||||||
new ListItem<>(AccountSessionManager.get(accountID).domain, getString(R.string.settings_server_explanation), R.drawable.ic_dns_24px, this::onServerClick),
|
new ListItem<>(AccountSessionManager.get(accountID).domain, getString(R.string.settings_server_explanation), R.drawable.ic_dns_24px, this::onServerClick),
|
||||||
new ListItem<>(getString(R.string.about_app, getString(R.string.app_name)), null, R.drawable.ic_info_24px, this::onAboutClick, null, 0, true),
|
new ListItem<>(getString(R.string.about_app, getString(R.string.app_name)), null, R.drawable.ic_info_24px, this::onAboutClick, null, 0, true),
|
||||||
new ListItem<>(R.string.manage_accounts, 0, R.drawable.ic_switch_account_24px, this::onManageAccountsClick),
|
|
||||||
new ListItem<>(R.string.log_out, 0, R.drawable.ic_logout_24px, this::onLogOutClick, R.attr.colorM3Error, false)
|
new ListItem<>(R.string.log_out, 0, R.drawable.ic_logout_24px, this::onLogOutClick, R.attr.colorM3Error, false)
|
||||||
));
|
));
|
||||||
|
|
||||||
if(BuildConfig.DEBUG || BuildConfig.BUILD_TYPE.equals("appcenterPrivateBeta")){
|
if(BuildConfig.DEBUG || BuildConfig.BUILD_TYPE.equals("appcenterPrivateBeta")){
|
||||||
data.add(0, new ListItem<>("Debug settings", null, R.drawable.ic_settings_24px, i->Nav.go(getActivity(), SettingsDebugFragment.class, makeFragmentArgs()), null, 0, true));
|
data.add(0, new ListItem<>("Debug settings", null, R.drawable.ic_settings_24px, ()->Nav.go(getActivity(), SettingsDebugFragment.class, makeFragmentArgs()), null, 0, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountSession session=AccountSessionManager.get(accountID);
|
AccountSession session=AccountSessionManager.get(accountID);
|
||||||
@@ -124,45 +122,43 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
|
|||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onBehaviorClick(ListItem<?> item_){
|
private void onBehaviorClick(){
|
||||||
Nav.go(getActivity(), SettingsBehaviorFragment.class, makeFragmentArgs());
|
Nav.go(getActivity(), SettingsBehaviorFragment.class, makeFragmentArgs());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDisplayClick(ListItem<?> item_){
|
private void onDisplayClick(){
|
||||||
Nav.go(getActivity(), SettingsDisplayFragment.class, makeFragmentArgs());
|
Nav.go(getActivity(), SettingsDisplayFragment.class, makeFragmentArgs());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onPrivacyClick(ListItem<?> item_){
|
private void onPrivacyClick(){
|
||||||
Nav.go(getActivity(), SettingsPrivacyFragment.class, makeFragmentArgs());
|
Nav.go(getActivity(), SettingsPrivacyFragment.class, makeFragmentArgs());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onFiltersClick(ListItem<?> item_){
|
private void onFiltersClick(){
|
||||||
Nav.go(getActivity(), SettingsFiltersFragment.class, makeFragmentArgs());
|
Nav.go(getActivity(), SettingsFiltersFragment.class, makeFragmentArgs());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onNotificationsClick(ListItem<?> item_){
|
private void onNotificationsClick(){
|
||||||
Nav.go(getActivity(), SettingsNotificationsFragment.class, makeFragmentArgs());
|
Nav.go(getActivity(), SettingsNotificationsFragment.class, makeFragmentArgs());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onServerClick(ListItem<?> item_){
|
private void onServerClick(){
|
||||||
Nav.go(getActivity(), SettingsServerFragment.class, makeFragmentArgs());
|
Nav.go(getActivity(), SettingsServerFragment.class, makeFragmentArgs());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onAboutClick(ListItem<?> item_){
|
private void onAboutClick(){
|
||||||
Nav.go(getActivity(), SettingsAboutAppFragment.class, makeFragmentArgs());
|
Nav.go(getActivity(), SettingsAboutAppFragment.class, makeFragmentArgs());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onManageAccountsClick(ListItem<?> item){
|
private void onLogOutClick(){
|
||||||
new AccountSwitcherSheet(getActivity(), null).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onLogOutClick(ListItem<?> item_){
|
|
||||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
new M3AlertDialogBuilder(getActivity())
|
new M3AlertDialogBuilder(getActivity())
|
||||||
.setMessage(getString(R.string.confirm_log_out, session.getFullUsername()))
|
.setMessage(getString(R.string.confirm_log_out, session.getFullUsername()))
|
||||||
.setPositiveButton(R.string.log_out, (dialog, which)->AccountSessionManager.get(accountID).logOut(getActivity(), ()->{
|
.setPositiveButton(R.string.log_out, (dialog, which)->AccountSessionManager.get(accountID).logOut(getActivity(), ()->{
|
||||||
loggedOut=true;
|
loggedOut=true;
|
||||||
((MainActivity)getActivity()).restartHomeFragment();
|
getActivity().finish();
|
||||||
|
Intent intent=new Intent(getActivity(), MainActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
}))
|
}))
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
.show();
|
.show();
|
||||||
|
|||||||
@@ -55,14 +55,14 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
|
|||||||
getPushSubscription();
|
getPushSubscription();
|
||||||
|
|
||||||
onDataLoaded(List.of(
|
onDataLoaded(List.of(
|
||||||
pauseItem=new CheckableListItem<>(getString(R.string.pause_all_notifications), getPauseItemSubtitle(), CheckableListItem.Style.SWITCH, false, R.drawable.ic_notifications_paused_24px, i->onPauseNotificationsClick(false)),
|
pauseItem=new CheckableListItem<>(getString(R.string.pause_all_notifications), getPauseItemSubtitle(), CheckableListItem.Style.SWITCH, false, R.drawable.ic_notifications_paused_24px, ()->onPauseNotificationsClick(false)),
|
||||||
policyItem=new ListItem<>(R.string.settings_notifications_policy, 0, R.drawable.ic_group_24px, this::onNotificationsPolicyClick),
|
policyItem=new ListItem<>(R.string.settings_notifications_policy, 0, R.drawable.ic_group_24px, this::onNotificationsPolicyClick),
|
||||||
|
|
||||||
mentionsItem=new CheckableListItem<>(R.string.notification_type_mentions_and_replies, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.mention, this::toggleCheckableItem),
|
mentionsItem=new CheckableListItem<>(R.string.notification_type_mentions_and_replies, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.mention, ()->toggleCheckableItem(mentionsItem)),
|
||||||
boostsItem=new CheckableListItem<>(R.string.notification_type_reblog, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.reblog, this::toggleCheckableItem),
|
boostsItem=new CheckableListItem<>(R.string.notification_type_reblog, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.reblog, ()->toggleCheckableItem(boostsItem)),
|
||||||
favoritesItem=new CheckableListItem<>(R.string.notification_type_favorite, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.favourite, this::toggleCheckableItem),
|
favoritesItem=new CheckableListItem<>(R.string.notification_type_favorite, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.favourite, ()->toggleCheckableItem(favoritesItem)),
|
||||||
followersItem=new CheckableListItem<>(R.string.notification_type_follow, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.follow, this::toggleCheckableItem),
|
followersItem=new CheckableListItem<>(R.string.notification_type_follow, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.follow, ()->toggleCheckableItem(followersItem)),
|
||||||
pollsItem=new CheckableListItem<>(R.string.notification_type_poll, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.poll, this::toggleCheckableItem)
|
pollsItem=new CheckableListItem<>(R.string.notification_type_poll, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.poll, ()->toggleCheckableItem(pollsItem))
|
||||||
));
|
));
|
||||||
|
|
||||||
typeItems=List.of(mentionsItem, boostsItem, favoritesItem, followersItem, pollsItem);
|
typeItems=List.of(mentionsItem, boostsItem, favoritesItem, followersItem, pollsItem);
|
||||||
@@ -209,7 +209,7 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
|
|||||||
alert.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
alert.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onNotificationsPolicyClick(ListItem<?> item_){
|
private void onNotificationsPolicyClick(){
|
||||||
String[] items=Stream.of(
|
String[] items=Stream.of(
|
||||||
R.string.notifications_policy_anyone,
|
R.string.notifications_policy_anyone,
|
||||||
R.string.notifications_policy_followed,
|
R.string.notifications_policy_followed,
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ public class SettingsPrivacyFragment extends BaseSettingsFragment<Void>{
|
|||||||
setTitle(R.string.settings_privacy);
|
setTitle(R.string.settings_privacy);
|
||||||
Account self=AccountSessionManager.get(accountID).self;
|
Account self=AccountSessionManager.get(accountID).self;
|
||||||
onDataLoaded(List.of(
|
onDataLoaded(List.of(
|
||||||
discoverableItem=new CheckableListItem<>(R.string.settings_discoverable, 0, CheckableListItem.Style.SWITCH, self.discoverable, R.drawable.ic_thumbs_up_down_24px, this::toggleCheckableItem),
|
discoverableItem=new CheckableListItem<>(R.string.settings_discoverable, 0, CheckableListItem.Style.SWITCH, self.discoverable, R.drawable.ic_thumbs_up_down_24px, ()->toggleCheckableItem(discoverableItem)),
|
||||||
indexableItem=new CheckableListItem<>(R.string.settings_indexable, 0, CheckableListItem.Style.SWITCH, self.source.indexable!=null ? self.source.indexable : true, R.drawable.ic_search_24px, this::toggleCheckableItem)
|
indexableItem=new CheckableListItem<>(R.string.settings_indexable, 0, CheckableListItem.Style.SWITCH, self.source.indexable!=null ? self.source.indexable : true, R.drawable.ic_search_24px, ()->toggleCheckableItem(indexableItem))
|
||||||
));
|
));
|
||||||
if(self.source.indexable==null)
|
if(self.source.indexable==null)
|
||||||
indexableItem.isEnabled=false;
|
indexableItem.isEnabled=false;
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ public class SettingsServerAboutFragment extends LoaderFragment{
|
|||||||
if(!TextUtils.isEmpty(instance.email)){
|
if(!TextUtils.isEmpty(instance.email)){
|
||||||
needDivider=true;
|
needDivider=true;
|
||||||
SimpleListItemViewHolder holder=new SimpleListItemViewHolder(getActivity(), scrollingLayout);
|
SimpleListItemViewHolder holder=new SimpleListItemViewHolder(getActivity(), scrollingLayout);
|
||||||
ListItem<Void> item=new ListItem<>(R.string.send_email_to_server_admin, 0, R.drawable.ic_mail_24px, i->{});
|
ListItem<Void> item=new ListItem<>(R.string.send_email_to_server_admin, 0, R.drawable.ic_mail_24px, ()->{});
|
||||||
holder.bind(item);
|
holder.bind(item);
|
||||||
holder.itemView.setBackground(UiUtils.getThemeDrawable(getActivity(), android.R.attr.selectableItemBackground));
|
holder.itemView.setBackground(UiUtils.getThemeDrawable(getActivity(), android.R.attr.selectableItemBackground));
|
||||||
holder.itemView.setOnClickListener(v->openAdminEmail());
|
holder.itemView.setOnClickListener(v->openAdminEmail());
|
||||||
|
|||||||
@@ -146,21 +146,18 @@ public class SettingsServerFragment extends AppKitFragment{
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
FrameLayout view=new FrameLayout(parent.getContext());
|
FrameLayout view=tabViews[viewType];
|
||||||
|
((ViewGroup)view.getParent()).removeView(view);
|
||||||
|
view.setVisibility(View.VISIBLE);
|
||||||
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
return new SimpleViewHolder(view);
|
return new SimpleViewHolder(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
|
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
|
||||||
FrameLayout view=tabViews[position];
|
|
||||||
if(view.getParent() instanceof ViewGroup parent)
|
|
||||||
parent.removeView(view);
|
|
||||||
view.setVisibility(View.VISIBLE);
|
|
||||||
((FrameLayout)holder.itemView).addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
|
||||||
Fragment fragment=getFragmentForPage(position);
|
Fragment fragment=getFragmentForPage(position);
|
||||||
if(!fragment.isAdded()){
|
if(!fragment.isAdded()){
|
||||||
getChildFragmentManager().beginTransaction().add(view.getId(), fragment).commit();
|
getChildFragmentManager().beginTransaction().add(holder.itemView.getId(), fragment).commit();
|
||||||
holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreDraw(){
|
public boolean onPreDraw(){
|
||||||
|
|||||||
@@ -45,34 +45,26 @@ public class Attachment extends BaseModel{
|
|||||||
|
|
||||||
public int getWidth(){
|
public int getWidth(){
|
||||||
if(meta==null)
|
if(meta==null)
|
||||||
return 1920;
|
return 0;
|
||||||
if(meta.width>0)
|
if(meta.width>0)
|
||||||
return meta.width;
|
return meta.width;
|
||||||
if(meta.original!=null && meta.original.width>0)
|
if(meta.original!=null && meta.original.width>0)
|
||||||
return meta.original.width;
|
return meta.original.width;
|
||||||
if(meta.small!=null && meta.small.width>0)
|
if(meta.small!=null && meta.small.width>0)
|
||||||
return meta.small.width;
|
return meta.small.width;
|
||||||
return 1920;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getHeight(){
|
public int getHeight(){
|
||||||
if(meta==null)
|
if(meta==null)
|
||||||
return 1080;
|
return 0;
|
||||||
if(meta.height>0)
|
if(meta.height>0)
|
||||||
return meta.height;
|
return meta.height;
|
||||||
if(meta.original!=null && meta.original.height>0)
|
if(meta.original!=null && meta.original.height>0)
|
||||||
return meta.original.height;
|
return meta.original.height;
|
||||||
if(meta.small!=null && meta.small.height>0)
|
if(meta.small!=null && meta.small.height>0)
|
||||||
return meta.small.height;
|
return meta.small.height;
|
||||||
return 1080;
|
return 0;
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasKnownDimensions(){
|
|
||||||
return meta!=null && (
|
|
||||||
(meta.height>0 && meta.width>0)
|
|
||||||
|| (meta.original!=null && meta.original.height>0 && meta.original.width>0)
|
|
||||||
|| (meta.small!=null && meta.small.height>0 && meta.small.width>0)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getDuration(){
|
public double getDuration(){
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
package org.joinmastodon.android.model;
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.AllFieldsAreRequired;
|
|
||||||
import org.joinmastodon.android.api.ObjectValidationException;
|
|
||||||
import org.parceler.Parcel;
|
|
||||||
|
|
||||||
// Called like this to avoid conflict with java.util.List
|
|
||||||
@AllFieldsAreRequired
|
|
||||||
@Parcel
|
|
||||||
public class FollowList extends BaseModel{
|
|
||||||
public String id;
|
|
||||||
public String title;
|
|
||||||
public RepliesPolicy repliesPolicy=RepliesPolicy.LIST;
|
|
||||||
public boolean exclusive;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString(){
|
|
||||||
return "FollowList{"+
|
|
||||||
"id='"+id+'\''+
|
|
||||||
", title='"+title+'\''+
|
|
||||||
", repliesPolicy="+repliesPolicy+
|
|
||||||
", exclusive="+exclusive+
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void postprocess() throws ObjectValidationException{
|
|
||||||
if(repliesPolicy==null)
|
|
||||||
repliesPolicy=RepliesPolicy.LIST;
|
|
||||||
super.postprocess();
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum RepliesPolicy{
|
|
||||||
@SerializedName("followed")
|
|
||||||
FOLLOWED,
|
|
||||||
@SerializedName("list")
|
|
||||||
LIST,
|
|
||||||
@SerializedName("none")
|
|
||||||
NONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -30,23 +30,4 @@ public class Hashtag extends BaseModel implements DisplayItemsParent{
|
|||||||
public String getID(){
|
public String getID(){
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o){
|
|
||||||
if(this==o) return true;
|
|
||||||
if(o==null || getClass()!=o.getClass()) return false;
|
|
||||||
|
|
||||||
Hashtag hashtag=(Hashtag) o;
|
|
||||||
|
|
||||||
return name.equals(hashtag.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode(){
|
|
||||||
return name.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getWeekPosts(){
|
|
||||||
return history.stream().mapToInt(h->h.uses).sum();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,10 +21,4 @@ public class HeaderPaginationList<T> extends ArrayList<T>{
|
|||||||
public HeaderPaginationList(@NonNull Collection<? extends T> c){
|
public HeaderPaginationList(@NonNull Collection<? extends T> c){
|
||||||
super(c);
|
super(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getNextPageMaxID(){
|
|
||||||
if(nextPageUri==null)
|
|
||||||
return null;
|
|
||||||
return nextPageUri.getQueryParameter("max_id");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,22 +20,4 @@ public class Mention extends BaseModel{
|
|||||||
", url='"+url+'\''+
|
", url='"+url+'\''+
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o){
|
|
||||||
if(this==o) return true;
|
|
||||||
if(o==null || getClass()!=o.getClass()) return false;
|
|
||||||
|
|
||||||
Mention mention=(Mention) o;
|
|
||||||
|
|
||||||
if(!id.equals(mention.id)) return false;
|
|
||||||
return url.equals(mention.url);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode(){
|
|
||||||
int result=id.hashCode();
|
|
||||||
result=31*result+url.hashCode();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public class Status extends BaseModel implements DisplayItemsParent{
|
|||||||
public StatusPrivacy visibility;
|
public StatusPrivacy visibility;
|
||||||
public boolean sensitive;
|
public boolean sensitive;
|
||||||
@RequiredField
|
@RequiredField
|
||||||
public String spoilerText="";
|
public String spoilerText;
|
||||||
@RequiredField
|
@RequiredField
|
||||||
public List<Attachment> mediaAttachments;
|
public List<Attachment> mediaAttachments;
|
||||||
public Application application;
|
public Application application;
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
package org.joinmastodon.android.model.viewmodel;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
|
||||||
|
|
||||||
public class AvatarPileListItem<T> extends ListItem<T>{
|
|
||||||
public List<ImageLoaderRequest> avatars;
|
|
||||||
|
|
||||||
public AvatarPileListItem(String title, String subtitle, List<ImageLoaderRequest> avatars, int iconRes, Consumer<AvatarPileListItem<T>> onClick, T parentObject, boolean dividerAfter){
|
|
||||||
super(title, subtitle, iconRes, (Consumer<ListItem<T>>)(Object)onClick, parentObject, 0, dividerAfter);
|
|
||||||
this.avatars=avatars;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemViewType(){
|
|
||||||
return R.id.list_item_avatar_pile;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,42 +9,42 @@ public class CheckableListItem<T> extends ListItem<T>{
|
|||||||
public boolean checked;
|
public boolean checked;
|
||||||
public Consumer<Boolean> checkedChangeListener;
|
public Consumer<Boolean> checkedChangeListener;
|
||||||
|
|
||||||
public CheckableListItem(String title, String subtitle, Style style, boolean checked, int iconRes, Consumer<CheckableListItem<T>> onClick, T parentObject, boolean dividerAfter){
|
public CheckableListItem(String title, String subtitle, Style style, boolean checked, int iconRes, Runnable onClick, T parentObject, boolean dividerAfter){
|
||||||
super(title, subtitle, iconRes, (Consumer<ListItem<T>>)(Object)onClick, parentObject, 0, dividerAfter);
|
super(title, subtitle, iconRes, onClick, parentObject, 0, dividerAfter);
|
||||||
this.style=style;
|
this.style=style;
|
||||||
this.checked=checked;
|
this.checked=checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CheckableListItem(String title, String subtitle, Style style, boolean checked, Consumer<CheckableListItem<T>> onClick){
|
public CheckableListItem(String title, String subtitle, Style style, boolean checked, Runnable onClick){
|
||||||
this(title, subtitle, style, checked, 0, onClick, null, false);
|
this(title, subtitle, style, checked, 0, onClick, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CheckableListItem(String title, String subtitle, Style style, boolean checked, Consumer<CheckableListItem<T>> onClick, T parentObject){
|
public CheckableListItem(String title, String subtitle, Style style, boolean checked, Runnable onClick, T parentObject){
|
||||||
this(title, subtitle, style, checked, 0, onClick, parentObject, false);
|
this(title, subtitle, style, checked, 0, onClick, parentObject, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CheckableListItem(String title, String subtitle, Style style, boolean checked, int iconRes, Consumer<CheckableListItem<T>> onClick){
|
public CheckableListItem(String title, String subtitle, Style style, boolean checked, int iconRes, Runnable onClick){
|
||||||
this(title, subtitle, style, checked, iconRes, onClick, null, false);
|
this(title, subtitle, style, checked, iconRes, onClick, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CheckableListItem(String title, String subtitle, Style style, boolean checked, int iconRes, Consumer<CheckableListItem<T>> onClick, T parentObject){
|
public CheckableListItem(String title, String subtitle, Style style, boolean checked, int iconRes, Runnable onClick, T parentObject){
|
||||||
this(title, subtitle, style, checked, iconRes, onClick, parentObject, false);
|
this(title, subtitle, style, checked, iconRes, onClick, parentObject, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CheckableListItem(int titleRes, int subtitleRes, Style style, boolean checked, Consumer<CheckableListItem<T>> onClick){
|
public CheckableListItem(int titleRes, int subtitleRes, Style style, boolean checked, Runnable onClick){
|
||||||
this(titleRes, subtitleRes, style, checked, 0, onClick, false);
|
this(titleRes, subtitleRes, style, checked, 0, onClick, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CheckableListItem(int titleRes, int subtitleRes, Style style, boolean checked, Consumer<CheckableListItem<T>> onClick, boolean dividerAfter){
|
public CheckableListItem(int titleRes, int subtitleRes, Style style, boolean checked, Runnable onClick, boolean dividerAfter){
|
||||||
this(titleRes, subtitleRes, style, checked, 0, onClick, dividerAfter);
|
this(titleRes, subtitleRes, style, checked, 0, onClick, dividerAfter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CheckableListItem(int titleRes, int subtitleRes, Style style, boolean checked, int iconRes, Consumer<CheckableListItem<T>> onClick){
|
public CheckableListItem(int titleRes, int subtitleRes, Style style, boolean checked, int iconRes, Runnable onClick){
|
||||||
this(titleRes, subtitleRes, style, checked, iconRes, onClick, false);
|
this(titleRes, subtitleRes, style, checked, iconRes, onClick, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CheckableListItem(int titleRes, int subtitleRes, Style style, boolean checked, int iconRes, Consumer<CheckableListItem<T>> onClick, boolean dividerAfter){
|
public CheckableListItem(int titleRes, int subtitleRes, Style style, boolean checked, int iconRes, Runnable onClick, boolean dividerAfter){
|
||||||
super(titleRes, subtitleRes, iconRes, (Consumer<ListItem<T>>)(Object)onClick, 0, dividerAfter);
|
super(titleRes, subtitleRes, iconRes, onClick, 0, dividerAfter);
|
||||||
this.style=style;
|
this.style=style;
|
||||||
this.checked=checked;
|
this.checked=checked;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package org.joinmastodon.android.model.viewmodel;
|
|||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes;
|
import androidx.annotation.DrawableRes;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
|
|
||||||
@@ -18,11 +16,11 @@ public class ListItem<T>{
|
|||||||
public int iconRes;
|
public int iconRes;
|
||||||
public int colorOverrideAttr;
|
public int colorOverrideAttr;
|
||||||
public boolean dividerAfter;
|
public boolean dividerAfter;
|
||||||
private Consumer<ListItem<T>> onClick;
|
public Runnable onClick;
|
||||||
public boolean isEnabled=true;
|
public boolean isEnabled=true;
|
||||||
public T parentObject;
|
public T parentObject;
|
||||||
|
|
||||||
public ListItem(String title, String subtitle, int iconRes, Consumer<ListItem<T>> onClick, T parentObject, int colorOverrideAttr, boolean dividerAfter){
|
public ListItem(String title, String subtitle, int iconRes, Runnable onClick, T parentObject, int colorOverrideAttr, boolean dividerAfter){
|
||||||
this.title=title;
|
this.title=title;
|
||||||
this.subtitle=subtitle;
|
this.subtitle=subtitle;
|
||||||
this.iconRes=iconRes;
|
this.iconRes=iconRes;
|
||||||
@@ -34,41 +32,41 @@ public class ListItem<T>{
|
|||||||
isEnabled=false;
|
isEnabled=false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListItem(String title, String subtitle, Consumer<ListItem<T>> onClick){
|
public ListItem(String title, String subtitle, Runnable onClick){
|
||||||
this(title, subtitle, 0, onClick, null, 0, false);
|
this(title, subtitle, 0, onClick, null, 0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListItem(String title, String subtitle, Consumer<ListItem<T>> onClick, T parentObject){
|
public ListItem(String title, String subtitle, Runnable onClick, T parentObject){
|
||||||
this(title, subtitle, 0, onClick, parentObject, 0, false);
|
this(title, subtitle, 0, onClick, parentObject, 0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListItem(String title, String subtitle, @DrawableRes int iconRes, Consumer<ListItem<T>> onClick){
|
public ListItem(String title, String subtitle, @DrawableRes int iconRes, Runnable onClick){
|
||||||
this(title, subtitle, iconRes, onClick, null, 0, false);
|
this(title, subtitle, iconRes, onClick, null, 0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListItem(String title, String subtitle, @DrawableRes int iconRes, Consumer<ListItem<T>> onClick, T parentObject){
|
public ListItem(String title, String subtitle, @DrawableRes int iconRes, Runnable onClick, T parentObject){
|
||||||
this(title, subtitle, iconRes, onClick, parentObject, 0, false);
|
this(title, subtitle, iconRes, onClick, parentObject, 0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListItem(@StringRes int titleRes, @StringRes int subtitleRes, Consumer<ListItem<T>> onClick){
|
public ListItem(@StringRes int titleRes, @StringRes int subtitleRes, Runnable onClick){
|
||||||
this(null, null, 0, onClick, null, 0, false);
|
this(null, null, 0, onClick, null, 0, false);
|
||||||
this.titleRes=titleRes;
|
this.titleRes=titleRes;
|
||||||
this.subtitleRes=subtitleRes;
|
this.subtitleRes=subtitleRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListItem(@StringRes int titleRes, @StringRes int subtitleRes, Consumer<ListItem<T>> onClick, int colorOverrideAttr, boolean dividerAfter){
|
public ListItem(@StringRes int titleRes, @StringRes int subtitleRes, Runnable onClick, int colorOverrideAttr, boolean dividerAfter){
|
||||||
this(null, null, 0, onClick, null, colorOverrideAttr, dividerAfter);
|
this(null, null, 0, onClick, null, colorOverrideAttr, dividerAfter);
|
||||||
this.titleRes=titleRes;
|
this.titleRes=titleRes;
|
||||||
this.subtitleRes=subtitleRes;
|
this.subtitleRes=subtitleRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListItem(@StringRes int titleRes, @StringRes int subtitleRes, @DrawableRes int iconRes, Consumer<ListItem<T>> onClick){
|
public ListItem(@StringRes int titleRes, @StringRes int subtitleRes, @DrawableRes int iconRes, Runnable onClick){
|
||||||
this(null, null, iconRes, onClick, null, 0, false);
|
this(null, null, iconRes, onClick, null, 0, false);
|
||||||
this.titleRes=titleRes;
|
this.titleRes=titleRes;
|
||||||
this.subtitleRes=subtitleRes;
|
this.subtitleRes=subtitleRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListItem(@StringRes int titleRes, @StringRes int subtitleRes, @DrawableRes int iconRes, Consumer<ListItem<T>> onClick, int colorOverrideAttr, boolean dividerAfter){
|
public ListItem(@StringRes int titleRes, @StringRes int subtitleRes, @DrawableRes int iconRes, Runnable onClick, int colorOverrideAttr, boolean dividerAfter){
|
||||||
this(null, null, iconRes, onClick, null, colorOverrideAttr, dividerAfter);
|
this(null, null, iconRes, onClick, null, colorOverrideAttr, dividerAfter);
|
||||||
this.titleRes=titleRes;
|
this.titleRes=titleRes;
|
||||||
this.subtitleRes=subtitleRes;
|
this.subtitleRes=subtitleRes;
|
||||||
@@ -77,13 +75,4 @@ public class ListItem<T>{
|
|||||||
public int getItemViewType(){
|
public int getItemViewType(){
|
||||||
return colorOverrideAttr==0 ? R.id.list_item_simple : R.id.list_item_simple_tinted;
|
return colorOverrideAttr==0 ? R.id.list_item_simple : R.id.list_item_simple_tinted;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void performClick(){
|
|
||||||
if(onClick!=null)
|
|
||||||
onClick.accept(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <I extends ListItem<T>> void setOnClick(Consumer<I> onClick){
|
|
||||||
this.onClick=(Consumer<ListItem<T>>) onClick;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
package org.joinmastodon.android.model.viewmodel;
|
|
||||||
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
public class ListItemWithOptionsMenu<T> extends ListItem<T>{
|
|
||||||
public OptionsMenuListener<T> listener;
|
|
||||||
|
|
||||||
public ListItemWithOptionsMenu(String title, String subtitle, OptionsMenuListener<T> listener, int iconRes, Consumer<ListItemWithOptionsMenu<T>> onClick, T parentObject, boolean dividerAfter){
|
|
||||||
super(title, subtitle, iconRes, (Consumer<ListItem<T>>)(Object)onClick, parentObject, 0, dividerAfter);
|
|
||||||
this.listener=listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemViewType(){
|
|
||||||
return R.id.list_item_options;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void performConfigureMenu(Menu menu){
|
|
||||||
listener.onConfigureListItemOptionsMenu(this, menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void performItemSelected(MenuItem item){
|
|
||||||
listener.onListItemOptionSelected(this, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OptionsMenuListener<T>{
|
|
||||||
void onConfigureListItemOptionsMenu(ListItemWithOptionsMenu<T> item, Menu menu);
|
|
||||||
void onListItemOptionSelected(ListItemWithOptionsMenu<T> item, MenuItem menuItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,6 @@ import android.graphics.drawable.Animatable;
|
|||||||
import android.graphics.drawable.ColorDrawable;
|
import android.graphics.drawable.ColorDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
@@ -37,7 +36,6 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import me.grishka.appkit.FragmentStackActivity;
|
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
@@ -115,7 +113,9 @@ public class AccountSwitcherSheet extends BottomSheet{
|
|||||||
private void logOut(String accountID){
|
private void logOut(String accountID){
|
||||||
AccountSessionManager.get(accountID).logOut(activity, ()->{
|
AccountSessionManager.get(accountID).logOut(activity, ()->{
|
||||||
dismiss();
|
dismiss();
|
||||||
((MainActivity)activity).restartHomeFragment();
|
activity.finish();
|
||||||
|
Intent intent=new Intent(activity, MainActivity.class);
|
||||||
|
activity.startActivity(intent);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,17 +248,17 @@ public class AccountSwitcherSheet extends BottomSheet{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(){
|
public void onClick(){
|
||||||
dismiss();
|
|
||||||
if(AccountSessionManager.getInstance().getLastActiveAccountID().equals(item.getID())){
|
if(AccountSessionManager.getInstance().getLastActiveAccountID().equals(item.getID())){
|
||||||
|
dismiss();
|
||||||
if(fragment!=null){
|
if(fragment!=null){
|
||||||
fragment.setCurrentTab(R.id.tab_profile);
|
fragment.setCurrentTab(R.id.tab_profile);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(AccountSessionManager.getInstance().tryGetAccount(item.getID())!=null){
|
if(AccountSessionManager.getInstance().tryGetAccount(item.getID())!=null)
|
||||||
AccountSessionManager.getInstance().setLastActiveAccountID(item.getID());
|
AccountSessionManager.getInstance().setLastActiveAccountID(item.getID());
|
||||||
((MainActivity)activity).restartHomeFragment();
|
activity.finish();
|
||||||
}
|
activity.startActivity(new Intent(activity, MainActivity.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public class SearchViewHelper{
|
|||||||
searchEdit.setBackground(null);
|
searchEdit.setBackground(null);
|
||||||
searchEdit.addTextChangedListener(new SimpleTextWatcher(e->{
|
searchEdit.addTextChangedListener(new SimpleTextWatcher(e->{
|
||||||
searchEdit.removeCallbacks(debouncer);
|
searchEdit.removeCallbacks(debouncer);
|
||||||
searchEdit.postDelayed(debouncer, 500);
|
searchEdit.postDelayed(debouncer, 300);
|
||||||
boolean newIsEmpty=e.length()==0;
|
boolean newIsEmpty=e.length()==0;
|
||||||
if(isEmpty!=newIsEmpty){
|
if(isEmpty!=newIsEmpty){
|
||||||
isEmpty=newIsEmpty;
|
isEmpty=newIsEmpty;
|
||||||
|
|||||||
@@ -3,12 +3,9 @@ package org.joinmastodon.android.ui.adapters;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.model.viewmodel.AvatarPileListItem;
|
|
||||||
import org.joinmastodon.android.model.viewmodel.ListItem;
|
import org.joinmastodon.android.model.viewmodel.ListItem;
|
||||||
import org.joinmastodon.android.ui.viewholders.AvatarPileListItemViewHolder;
|
|
||||||
import org.joinmastodon.android.ui.viewholders.CheckboxOrRadioListItemViewHolder;
|
import org.joinmastodon.android.ui.viewholders.CheckboxOrRadioListItemViewHolder;
|
||||||
import org.joinmastodon.android.ui.viewholders.ListItemViewHolder;
|
import org.joinmastodon.android.ui.viewholders.ListItemViewHolder;
|
||||||
import org.joinmastodon.android.ui.viewholders.OptionsListItemViewHolder;
|
|
||||||
import org.joinmastodon.android.ui.viewholders.SimpleListItemViewHolder;
|
import org.joinmastodon.android.ui.viewholders.SimpleListItemViewHolder;
|
||||||
import org.joinmastodon.android.ui.viewholders.SwitchListItemViewHolder;
|
import org.joinmastodon.android.ui.viewholders.SwitchListItemViewHolder;
|
||||||
|
|
||||||
@@ -16,21 +13,11 @@ import java.util.List;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
|
||||||
import me.grishka.appkit.imageloader.ListImageLoaderWrapper;
|
|
||||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
|
||||||
|
|
||||||
public class GenericListItemsAdapter<T> extends UsableRecyclerView.Adapter<ListItemViewHolder<?>> implements ImageLoaderRecyclerAdapter{
|
public class GenericListItemsAdapter<T> extends RecyclerView.Adapter<ListItemViewHolder<?>>{
|
||||||
private List<ListItem<T>> items;
|
private List<ListItem<T>> items;
|
||||||
|
|
||||||
public GenericListItemsAdapter(List<ListItem<T>> items){
|
public GenericListItemsAdapter(List<ListItem<T>> items){
|
||||||
super(null);
|
|
||||||
this.items=items;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GenericListItemsAdapter(ListImageLoaderWrapper imgLoader, List<ListItem<T>> items){
|
|
||||||
super(imgLoader);
|
|
||||||
this.items=items;
|
this.items=items;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,10 +32,6 @@ public class GenericListItemsAdapter<T> extends UsableRecyclerView.Adapter<ListI
|
|||||||
return new CheckboxOrRadioListItemViewHolder(parent.getContext(), parent, false);
|
return new CheckboxOrRadioListItemViewHolder(parent.getContext(), parent, false);
|
||||||
if(viewType==R.id.list_item_radio)
|
if(viewType==R.id.list_item_radio)
|
||||||
return new CheckboxOrRadioListItemViewHolder(parent.getContext(), parent, true);
|
return new CheckboxOrRadioListItemViewHolder(parent.getContext(), parent, true);
|
||||||
if(viewType==R.id.list_item_options)
|
|
||||||
return new OptionsListItemViewHolder(parent.getContext(), parent);
|
|
||||||
if(viewType==R.id.list_item_avatar_pile)
|
|
||||||
return new AvatarPileListItemViewHolder(parent.getContext(), parent);
|
|
||||||
|
|
||||||
throw new IllegalArgumentException("Unexpected view type "+viewType);
|
throw new IllegalArgumentException("Unexpected view type "+viewType);
|
||||||
}
|
}
|
||||||
@@ -68,20 +51,4 @@ public class GenericListItemsAdapter<T> extends UsableRecyclerView.Adapter<ListI
|
|||||||
public int getItemViewType(int position){
|
public int getItemViewType(int position){
|
||||||
return items.get(position).getItemViewType();
|
return items.get(position).getItemViewType();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getImageCountForItem(int position){
|
|
||||||
ListItem<?> item=items.get(position);
|
|
||||||
if(item instanceof AvatarPileListItem<?> avatarPileListItem)
|
|
||||||
return avatarPileListItem.avatars.size();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ImageLoaderRequest getImageRequest(int position, int image){
|
|
||||||
ListItem<?> item=items.get(position);
|
|
||||||
if(item instanceof AvatarPileListItem<?> avatarPileListItem)
|
|
||||||
return avatarPileListItem.avatars.get(image);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ package org.joinmastodon.android.ui.displayitems;
|
|||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.ClipData;
|
|
||||||
import android.content.ClipboardManager;
|
|
||||||
import android.graphics.drawable.Animatable;
|
import android.graphics.drawable.Animatable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
@@ -26,7 +24,6 @@ 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.statuses.GetStatusSourceText;
|
import org.joinmastodon.android.api.requests.statuses.GetStatusSourceText;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.AddAccountToListsFragment;
|
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
@@ -201,14 +198,6 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
UiUtils.openSystemShareSheet(activity, item.status.url);
|
UiUtils.openSystemShareSheet(activity, item.status.url);
|
||||||
}else if(id==R.id.translate){
|
}else if(id==R.id.translate){
|
||||||
item.parentFragment.togglePostTranslation(item.status, item.parentID);
|
item.parentFragment.togglePostTranslation(item.status, item.parentID);
|
||||||
}else if(id==R.id.add_to_list){
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", item.parentFragment.getAccountID());
|
|
||||||
args.putParcelable("targetAccount", Parcels.wrap(account));
|
|
||||||
Nav.go(activity, AddAccountToListsFragment.class, args);
|
|
||||||
}else if(id==R.id.copy_link){
|
|
||||||
activity.getSystemService(ClipboardManager.class).setPrimaryClip(ClipData.newPlainText(null, item.status.url));
|
|
||||||
UiUtils.maybeShowTextCopiedToast(activity);
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@@ -337,7 +326,6 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
report.setTitle(item.parentFragment.getString(R.string.report_user, account.displayName));
|
report.setTitle(item.parentFragment.getString(R.string.report_user, account.displayName));
|
||||||
follow.setTitle(item.parentFragment.getString(relationship!=null && relationship.following ? R.string.unfollow_user : R.string.follow_user, account.displayName));
|
follow.setTitle(item.parentFragment.getString(relationship!=null && relationship.following ? R.string.unfollow_user : R.string.follow_user, account.displayName));
|
||||||
}
|
}
|
||||||
menu.findItem(R.id.add_to_list).setVisible(relationship!=null && relationship.following);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,8 +75,7 @@ public class LinkCardStatusDisplayItem extends StatusDisplayItem{
|
|||||||
if(item.imgRequest!=null){
|
if(item.imgRequest!=null){
|
||||||
crossfadeDrawable.setSize(card.width, card.height);
|
crossfadeDrawable.setSize(card.width, card.height);
|
||||||
crossfadeDrawable.setBlurhashDrawable(card.blurhashPlaceholder);
|
crossfadeDrawable.setBlurhashDrawable(card.blurhashPlaceholder);
|
||||||
crossfadeDrawable.setCrossfadeAlpha(0f);
|
crossfadeDrawable.setCrossfadeAlpha(item.status.spoilerRevealed ? 0f : 1f);
|
||||||
photo.setImageDrawable(null);
|
|
||||||
photo.setImageDrawable(crossfadeDrawable);
|
photo.setImageDrawable(crossfadeDrawable);
|
||||||
didClear=false;
|
didClear=false;
|
||||||
}
|
}
|
||||||
@@ -85,14 +84,8 @@ public class LinkCardStatusDisplayItem extends StatusDisplayItem{
|
|||||||
@Override
|
@Override
|
||||||
public void setImage(int index, Drawable drawable){
|
public void setImage(int index, Drawable drawable){
|
||||||
crossfadeDrawable.setImageDrawable(drawable);
|
crossfadeDrawable.setImageDrawable(drawable);
|
||||||
if(didClear)
|
if(didClear && item.status.spoilerRevealed)
|
||||||
crossfadeDrawable.animateAlpha(0f);
|
crossfadeDrawable.animateAlpha(0f);
|
||||||
Card card=item.status.card;
|
|
||||||
// Make sure the image is not stretched if the server returned wrong dimensions
|
|
||||||
if(drawable!=null && (drawable.getIntrinsicWidth()!=card.width || drawable.getIntrinsicHeight()!=card.height)){
|
|
||||||
photo.setImageDrawable(null);
|
|
||||||
photo.setImageDrawable(crossfadeDrawable);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -102,7 +95,7 @@ public class LinkCardStatusDisplayItem extends StatusDisplayItem{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onClick(View v){
|
private void onClick(View v){
|
||||||
UiUtils.openURL(itemView.getContext(), item.parentFragment.getAccountID(), item.status.card.url);
|
UiUtils.launchWebBrowser(itemView.getContext(), item.status.card.url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,11 +125,11 @@ public abstract class StatusDisplayItem{
|
|||||||
items.add(spoilerItem);
|
items.add(spoilerItem);
|
||||||
contentItems=spoilerItem.contentItems;
|
contentItems=spoilerItem.contentItems;
|
||||||
status.spoilerRevealed=false;
|
status.spoilerRevealed=false;
|
||||||
}else if(!TextUtils.isEmpty(statusForContent.spoilerText)){
|
}else if(!TextUtils.isEmpty(statusForContent.spoilerText) && AccountSessionManager.get(accountID).getLocalPreferences().showCWs){
|
||||||
SpoilerStatusDisplayItem spoilerItem=new SpoilerStatusDisplayItem(parentID, fragment, null, status, statusForContent, Type.SPOILER);
|
SpoilerStatusDisplayItem spoilerItem=new SpoilerStatusDisplayItem(parentID, fragment, null, status, statusForContent, Type.SPOILER);
|
||||||
items.add(spoilerItem);
|
items.add(spoilerItem);
|
||||||
contentItems=spoilerItem.contentItems;
|
contentItems=spoilerItem.contentItems;
|
||||||
status.spoilerRevealed=!AccountSessionManager.get(accountID).getLocalPreferences().showCWs;
|
status.spoilerRevealed=false;
|
||||||
}else{
|
}else{
|
||||||
contentItems=items;
|
contentItems=items;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,10 +66,6 @@ public class BlurhashCrossfadeDrawable extends Drawable{
|
|||||||
|
|
||||||
public void setImageDrawable(Drawable imageDrawable){
|
public void setImageDrawable(Drawable imageDrawable){
|
||||||
this.imageDrawable=imageDrawable;
|
this.imageDrawable=imageDrawable;
|
||||||
if(imageDrawable!=null){
|
|
||||||
width=imageDrawable.getIntrinsicWidth();
|
|
||||||
height=imageDrawable.getIntrinsicHeight();
|
|
||||||
}
|
|
||||||
invalidateSelf();
|
invalidateSelf();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -716,18 +716,9 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
|||||||
public void onBind(Attachment item){
|
public void onBind(Attachment item){
|
||||||
super.onBind(item);
|
super.onBind(item);
|
||||||
FrameLayout.LayoutParams params=(FrameLayout.LayoutParams) imageView.getLayoutParams();
|
FrameLayout.LayoutParams params=(FrameLayout.LayoutParams) imageView.getLayoutParams();
|
||||||
Drawable currentDrawable=listener.getPhotoViewCurrentDrawable(getAbsoluteAdapterPosition());
|
params.width=item.getWidth();
|
||||||
if(item.hasKnownDimensions()){
|
params.height=item.getHeight();
|
||||||
params.width=item.getWidth();
|
ViewImageLoader.load(this, listener.getPhotoViewCurrentDrawable(getAbsoluteAdapterPosition()), new UrlImageLoaderRequest(item.url), false);
|
||||||
params.height=item.getHeight();
|
|
||||||
}else if(currentDrawable!=null){
|
|
||||||
params.width=currentDrawable.getIntrinsicWidth();
|
|
||||||
params.height=currentDrawable.getIntrinsicHeight();
|
|
||||||
}else{
|
|
||||||
params.width=1920;
|
|
||||||
params.height=1080;
|
|
||||||
}
|
|
||||||
ViewImageLoader.load(this, currentDrawable, new UrlImageLoaderRequest(item.url), false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -769,18 +760,9 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
|||||||
super.onBind(item);
|
super.onBind(item);
|
||||||
playerReady=false;
|
playerReady=false;
|
||||||
FrameLayout.LayoutParams params=(FrameLayout.LayoutParams) wrap.getLayoutParams();
|
FrameLayout.LayoutParams params=(FrameLayout.LayoutParams) wrap.getLayoutParams();
|
||||||
Drawable currentDrawable=listener.getPhotoViewCurrentDrawable(getAbsoluteAdapterPosition());
|
params.width=item.getWidth();
|
||||||
if(item.hasKnownDimensions()){
|
params.height=item.getHeight();
|
||||||
params.width=item.getWidth();
|
wrap.setBackground(listener.getPhotoViewCurrentDrawable(getAbsoluteAdapterPosition()));
|
||||||
params.height=item.getHeight();
|
|
||||||
}else if(currentDrawable!=null){
|
|
||||||
params.width=currentDrawable.getIntrinsicWidth();
|
|
||||||
params.height=currentDrawable.getIntrinsicHeight();
|
|
||||||
}else{
|
|
||||||
params.width=1920;
|
|
||||||
params.height=1080;
|
|
||||||
}
|
|
||||||
wrap.setBackground(currentDrawable);
|
|
||||||
progressBar.setVisibility(item.type==Attachment.Type.VIDEO ? View.VISIBLE : View.GONE);
|
progressBar.setVisibility(item.type==Attachment.Type.VIDEO ? View.VISIBLE : View.GONE);
|
||||||
if(itemView.isAttachedToWindow()){
|
if(itemView.isAttachedToWindow()){
|
||||||
reset();
|
reset();
|
||||||
@@ -840,9 +822,7 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
|||||||
@Override
|
@Override
|
||||||
public boolean onError(MediaPlayer mp, int what, int extra){
|
public boolean onError(MediaPlayer mp, int what, int extra){
|
||||||
Log.e(TAG, "video player onError() called with: mp = ["+mp+"], what = ["+what+"], extra = ["+extra+"]");
|
Log.e(TAG, "video player onError() called with: mp = ["+mp+"], what = ["+what+"], extra = ["+extra+"]");
|
||||||
Toast.makeText(activity, R.string.error_playing_video, Toast.LENGTH_SHORT).show();
|
return false;
|
||||||
onStartSwipeToDismissTransition(0f);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void prepareAndStartPlayer(){
|
public void prepareAndStartPlayer(){
|
||||||
@@ -863,8 +843,6 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
|||||||
player.prepareAsync();
|
player.prepareAsync();
|
||||||
}catch(IOException x){
|
}catch(IOException x){
|
||||||
Log.w(TAG, "Error initializing gif player", x);
|
Log.w(TAG, "Error initializing gif player", x);
|
||||||
Toast.makeText(activity, R.string.error_playing_video, Toast.LENGTH_SHORT).show();
|
|
||||||
onStartSwipeToDismissTransition(0f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -119,11 +119,6 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
|
|||||||
|
|
||||||
int width=right-left;
|
int width=right-left;
|
||||||
int height=bottom-top;
|
int height=bottom-top;
|
||||||
if(width==0 || height==0 || child.getWidth()==0 || child.getWidth()==0){
|
|
||||||
matrix.reset();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
float scale=Math.min(width/(float)child.getWidth(), height/(float)child.getHeight());
|
float scale=Math.min(width/(float)child.getWidth(), height/(float)child.getHeight());
|
||||||
minScale=scale;
|
minScale=scale;
|
||||||
maxScale=Math.max(3f, height/(float)child.getHeight());
|
maxScale=Math.max(3f, height/(float)child.getHeight());
|
||||||
@@ -311,6 +306,8 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
|
|||||||
}, 1f).setMinimumVisibleChange(DynamicAnimation.MIN_VISIBLE_CHANGE_ALPHA));
|
}, 1f).setMinimumVisibleChange(DynamicAnimation.MIN_VISIBLE_CHANGE_ALPHA));
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
|
if(animatingTransition)
|
||||||
|
Log.w(TAG, "updateViewTransform: ", new Throwable().fillInStackTrace());
|
||||||
child.setScaleX(matrixValues[Matrix.MSCALE_X]);
|
child.setScaleX(matrixValues[Matrix.MSCALE_X]);
|
||||||
child.setScaleY(matrixValues[Matrix.MSCALE_Y]);
|
child.setScaleY(matrixValues[Matrix.MSCALE_Y]);
|
||||||
child.setTranslationX(matrixValues[Matrix.MTRANS_X]);
|
child.setTranslationX(matrixValues[Matrix.MTRANS_X]);
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import android.widget.TextView;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import me.grishka.appkit.utils.CustomViewHelper;
|
import me.grishka.appkit.utils.CustomViewHelper;
|
||||||
@@ -131,7 +130,11 @@ public class ClickableLinksDelegate implements CustomViewHelper{
|
|||||||
//copy link text to clipboard
|
//copy link text to clipboard
|
||||||
ClipboardManager clipboard = (ClipboardManager) view.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
ClipboardManager clipboard = (ClipboardManager) view.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
clipboard.setPrimaryClip(ClipData.newPlainText("", selectedSpan.getLink()));
|
clipboard.setPrimaryClip(ClipData.newPlainText("", selectedSpan.getLink()));
|
||||||
UiUtils.maybeShowTextCopiedToast(view.getContext());
|
//show toast, android from S_V2 on has built-in popup, as documented in
|
||||||
|
//https://developer.android.com/develop/ui/views/touch-and-input/copy-paste#duplicate-notifications
|
||||||
|
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
|
||||||
|
Toast.makeText(view.getContext(), R.string.text_copied, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
//reset view
|
//reset view
|
||||||
resetAndInvalidate();
|
resetAndInvalidate();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,10 +81,10 @@ public class HtmlParser{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String> idsByUrl=mentions.stream().distinct().collect(Collectors.toMap(m->m.url, m->m.id));
|
Map<String, String> idsByUrl=mentions.stream().collect(Collectors.toMap(m->m.url, m->m.id));
|
||||||
// Hashtags in remote posts have remote URLs, these have local URLs so they don't match.
|
// Hashtags in remote posts have remote URLs, these have local URLs so they don't match.
|
||||||
// Map<String, String> tagsByUrl=tags.stream().collect(Collectors.toMap(t->t.url, t->t.name));
|
// Map<String, String> tagsByUrl=tags.stream().collect(Collectors.toMap(t->t.url, t->t.name));
|
||||||
Map<String, Hashtag> tagsByTag=tags.stream().distinct().collect(Collectors.toMap(t->t.name.toLowerCase(), Function.identity()));
|
Map<String, Hashtag> tagsByTag=tags.stream().collect(Collectors.toMap(t->t.name.toLowerCase(), Function.identity()));
|
||||||
|
|
||||||
final SpannableStringBuilder ssb=new SpannableStringBuilder();
|
final SpannableStringBuilder ssb=new SpannableStringBuilder();
|
||||||
Jsoup.parseBodyFragment(source).body().traverse(new NodeVisitor(){
|
Jsoup.parseBodyFragment(source).body().traverse(new NodeVisitor(){
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
package org.joinmastodon.android.ui.utils;
|
|
||||||
|
|
||||||
import android.animation.Animator;
|
|
||||||
import android.animation.AnimatorListenerAdapter;
|
|
||||||
import android.animation.IntEvaluator;
|
|
||||||
import android.animation.ObjectAnimator;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.view.ActionMode;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
|
|
||||||
import java.util.function.IntSupplier;
|
|
||||||
|
|
||||||
import me.grishka.appkit.FragmentStackActivity;
|
|
||||||
import me.grishka.appkit.fragments.AppKitFragment;
|
|
||||||
|
|
||||||
public class ActionModeHelper{
|
|
||||||
public static ActionMode startActionMode(AppKitFragment fragment, IntSupplier statusBarColorSupplier, ActionMode.Callback callback){
|
|
||||||
FragmentStackActivity activity=(FragmentStackActivity) fragment.getActivity();
|
|
||||||
return activity.startActionMode(new ActionMode.Callback(){
|
|
||||||
@Override
|
|
||||||
public boolean onCreateActionMode(ActionMode mode, Menu menu){
|
|
||||||
if(!callback.onCreateActionMode(mode, menu))
|
|
||||||
return false;
|
|
||||||
ObjectAnimator anim=ObjectAnimator.ofInt(activity.getWindow(), "statusBarColor", statusBarColorSupplier.getAsInt(), UiUtils.getThemeColor(activity, R.attr.colorM3Primary));
|
|
||||||
anim.setEvaluator(new IntEvaluator(){
|
|
||||||
@Override
|
|
||||||
public Integer evaluate(float fraction, Integer startValue, Integer endValue){
|
|
||||||
return UiUtils.alphaBlendColors(startValue, endValue, fraction);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
anim.start();
|
|
||||||
activity.invalidateSystemBarColors(fragment);
|
|
||||||
View fakeView=new View(activity);
|
|
||||||
// mode.setCustomView(fakeView);
|
|
||||||
// int buttonID=activity.getResources().getIdentifier("action_mode_close_button", "id", "android");
|
|
||||||
// View btn=activity.getWindow().getDecorView().findViewById(buttonID);
|
|
||||||
// if(btn!=null){
|
|
||||||
// ((ViewGroup.MarginLayoutParams)btn.getLayoutParams()).setMarginEnd(0);
|
|
||||||
// }
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu){
|
|
||||||
if(!callback.onPrepareActionMode(mode, menu))
|
|
||||||
return false;
|
|
||||||
for(int i=0;i<menu.size();i++){
|
|
||||||
Drawable icon=menu.getItem(i).getIcon();
|
|
||||||
if(icon!=null){
|
|
||||||
icon=icon.mutate();
|
|
||||||
icon.setTint(UiUtils.getThemeColor(activity, R.attr.colorM3OnPrimary));
|
|
||||||
menu.getItem(i).setIcon(icon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item){
|
|
||||||
return callback.onActionItemClicked(mode, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyActionMode(ActionMode mode){
|
|
||||||
ObjectAnimator anim=ObjectAnimator.ofInt(activity.getWindow(), "statusBarColor", UiUtils.getThemeColor(activity, R.attr.colorM3Primary), statusBarColorSupplier.getAsInt());
|
|
||||||
anim.setEvaluator(new IntEvaluator(){
|
|
||||||
@Override
|
|
||||||
public Integer evaluate(float fraction, Integer startValue, Integer endValue){
|
|
||||||
return UiUtils.alphaBlendColors(startValue, endValue, fraction);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
anim.addListener(new AnimatorListenerAdapter(){
|
|
||||||
@Override
|
|
||||||
public void onAnimationEnd(Animator animation){
|
|
||||||
activity.getWindow().setStatusBarColor(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
anim.start();
|
|
||||||
activity.invalidateSystemBarColors(fragment);
|
|
||||||
callback.onDestroyActionMode(mode);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -22,8 +22,6 @@ public class DiscoverInfoBannerHelper{
|
|||||||
private final BannerType type;
|
private final BannerType type;
|
||||||
private final String accountID;
|
private final String accountID;
|
||||||
private static EnumSet<BannerType> bannerTypesToShow=EnumSet.noneOf(BannerType.class);
|
private static EnumSet<BannerType> bannerTypesToShow=EnumSet.noneOf(BannerType.class);
|
||||||
private SingleViewRecyclerAdapter bannerAdapter;
|
|
||||||
private boolean added;
|
|
||||||
|
|
||||||
static{
|
static{
|
||||||
for(BannerType t:BannerType.values()){
|
for(BannerType t:BannerType.values()){
|
||||||
@@ -42,8 +40,6 @@ public class DiscoverInfoBannerHelper{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void maybeAddBanner(RecyclerView list, MergeRecyclerAdapter adapter){
|
public void maybeAddBanner(RecyclerView list, MergeRecyclerAdapter adapter){
|
||||||
if(added)
|
|
||||||
return;
|
|
||||||
if(bannerTypesToShow.contains(type)){
|
if(bannerTypesToShow.contains(type)){
|
||||||
banner=((Activity)list.getContext()).getLayoutInflater().inflate(R.layout.discover_info_banner, list, false);
|
banner=((Activity)list.getContext()).getLayoutInflater().inflate(R.layout.discover_info_banner, list, false);
|
||||||
TextView text=banner.findViewById(R.id.banner_text);
|
TextView text=banner.findViewById(R.id.banner_text);
|
||||||
@@ -60,8 +56,7 @@ public class DiscoverInfoBannerHelper{
|
|||||||
case LOCAL_TIMELINE -> R.drawable.ic_stream_24px;
|
case LOCAL_TIMELINE -> R.drawable.ic_stream_24px;
|
||||||
case ACCOUNTS -> R.drawable.ic_group_add_24px;
|
case ACCOUNTS -> R.drawable.ic_group_add_24px;
|
||||||
});
|
});
|
||||||
adapter.addAdapter(0, bannerAdapter=new SingleViewRecyclerAdapter(banner));
|
adapter.addAdapter(new SingleViewRecyclerAdapter(banner));
|
||||||
added=true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,13 +65,6 @@ public class DiscoverInfoBannerHelper{
|
|||||||
// bannerTypesToShow is not updated here on purpose so the banner keeps showing until the app is relaunched
|
// bannerTypesToShow is not updated here on purpose so the banner keeps showing until the app is relaunched
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeBanner(MergeRecyclerAdapter adapter){
|
|
||||||
if(bannerAdapter!=null){
|
|
||||||
adapter.removeAdapter(bannerAdapter);
|
|
||||||
added=false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void reset(){
|
public static void reset(){
|
||||||
SharedPreferences prefs=getPrefs();
|
SharedPreferences prefs=getPrefs();
|
||||||
SharedPreferences.Editor e=prefs.edit();
|
SharedPreferences.Editor e=prefs.edit();
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ public class MediaAttachmentViewController{
|
|||||||
private final Context context;
|
private final Context context;
|
||||||
private boolean didClear;
|
private boolean didClear;
|
||||||
private Status status;
|
private Status status;
|
||||||
private Attachment attachment;
|
|
||||||
|
|
||||||
public MediaAttachmentViewController(Context context, MediaGridStatusDisplayItem.GridItemType type){
|
public MediaAttachmentViewController(Context context, MediaGridStatusDisplayItem.GridItemType type){
|
||||||
view=context.getSystemService(LayoutInflater.class).inflate(switch(type){
|
view=context.getSystemService(LayoutInflater.class).inflate(switch(type){
|
||||||
@@ -51,7 +50,6 @@ public class MediaAttachmentViewController{
|
|||||||
|
|
||||||
public void bind(Attachment attachment, Status status){
|
public void bind(Attachment attachment, Status status){
|
||||||
this.status=status;
|
this.status=status;
|
||||||
this.attachment=attachment;
|
|
||||||
crossfadeDrawable.setSize(attachment.getWidth(), attachment.getHeight());
|
crossfadeDrawable.setSize(attachment.getWidth(), attachment.getHeight());
|
||||||
crossfadeDrawable.setBlurhashDrawable(attachment.blurhashPlaceholder);
|
crossfadeDrawable.setBlurhashDrawable(attachment.blurhashPlaceholder);
|
||||||
crossfadeDrawable.setCrossfadeAlpha(0f);
|
crossfadeDrawable.setCrossfadeAlpha(0f);
|
||||||
@@ -71,11 +69,6 @@ public class MediaAttachmentViewController{
|
|||||||
crossfadeDrawable.setImageDrawable(drawable);
|
crossfadeDrawable.setImageDrawable(drawable);
|
||||||
if(didClear)
|
if(didClear)
|
||||||
crossfadeDrawable.animateAlpha(0f);
|
crossfadeDrawable.animateAlpha(0f);
|
||||||
// Make sure the image is not stretched if the server returned wrong dimensions
|
|
||||||
if(drawable!=null && (drawable.getIntrinsicWidth()!=attachment.getWidth() || drawable.getIntrinsicHeight()!=attachment.getHeight())){
|
|
||||||
photo.setImageDrawable(null);
|
|
||||||
photo.setImageDrawable(crossfadeDrawable);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearImage(){
|
public void clearImage(){
|
||||||
|
|||||||
@@ -45,14 +45,12 @@ import android.widget.Toolbar;
|
|||||||
|
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
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.requests.accounts.SetAccountBlocked;
|
import org.joinmastodon.android.api.requests.accounts.SetAccountBlocked;
|
||||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
||||||
import org.joinmastodon.android.api.requests.accounts.SetAccountMuted;
|
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.search.GetSearchResults;
|
|
||||||
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.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
@@ -65,7 +63,6 @@ import org.joinmastodon.android.model.Account;
|
|||||||
import org.joinmastodon.android.model.Emoji;
|
import org.joinmastodon.android.model.Emoji;
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
import org.joinmastodon.android.model.Relationship;
|
import org.joinmastodon.android.model.Relationship;
|
||||||
import org.joinmastodon.android.model.SearchResults;
|
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
|
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
|
||||||
@@ -618,10 +615,10 @@ public class UiUtils{
|
|||||||
|
|
||||||
public static void openURL(Context context, String accountID, String url){
|
public static void openURL(Context context, String accountID, String url){
|
||||||
Uri uri=Uri.parse(url);
|
Uri uri=Uri.parse(url);
|
||||||
if(accountID!=null && "https".equals(uri.getScheme())){
|
if(accountID!=null && "https".equals(uri.getScheme()) && AccountSessionManager.getInstance().getAccount(accountID).domain.equalsIgnoreCase(uri.getAuthority())){
|
||||||
List<String> path=uri.getPathSegments();
|
List<String> path=uri.getPathSegments();
|
||||||
if(AccountSessionManager.getInstance().getAccount(accountID).domain.equalsIgnoreCase(uri.getAuthority()) && path.size()==2 && path.get(0).matches("^@[a-zA-Z0-9_]+$") && path.get(1).matches("^[0-9]+$")){
|
// Match URLs like https://mastodon.social/@Gargron/108132679274083591
|
||||||
// Match URLs like https://mastodon.social/@Gargron/108132679274083591
|
if(path.size()==2 && path.get(0).matches("^@[a-zA-Z0-9_]+$") && path.get(1).matches("^[0-9]+$")){
|
||||||
new GetStatusByID(path.get(1))
|
new GetStatusByID(path.get(1))
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
@@ -641,32 +638,6 @@ public class UiUtils{
|
|||||||
.wrapProgress((Activity)context, R.string.loading, true)
|
.wrapProgress((Activity)context, R.string.loading, true)
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
return;
|
return;
|
||||||
}else{
|
|
||||||
new GetSearchResults(url, null, true, null, 0, 0)
|
|
||||||
.setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(SearchResults result){
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
if(result.statuses!=null && !result.statuses.isEmpty()){
|
|
||||||
args.putParcelable("status", Parcels.wrap(result.statuses.get(0)));
|
|
||||||
Nav.go((Activity)context, ThreadFragment.class, args);
|
|
||||||
}else if(result.accounts!=null && !result.accounts.isEmpty()){
|
|
||||||
args.putParcelable("profileAccount", Parcels.wrap(result.accounts.get(0)));
|
|
||||||
Nav.go((Activity)context, ProfileFragment.class, args);
|
|
||||||
}else{
|
|
||||||
launchWebBrowser(context, url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
launchWebBrowser(context, url);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.wrapProgress((Activity)context, R.string.loading, true)
|
|
||||||
.exec(accountID);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
launchWebBrowser(context, url);
|
launchWebBrowser(context, url);
|
||||||
@@ -790,17 +761,6 @@ public class UiUtils{
|
|||||||
return insets;
|
return insets;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void applyBottomInsetToFAB(View fab, WindowInsets insets){
|
|
||||||
int inset;
|
|
||||||
if(Build.VERSION.SDK_INT>=29 && insets.getTappableElementInsets().bottom==0 /*&& wantsOverlaySystemNavigation()*/){
|
|
||||||
int bottomInset=insets.getSystemWindowInsetBottom();
|
|
||||||
inset=bottomInset>0 ? Math.max(V.dp(40), bottomInset) : 0;
|
|
||||||
}else{
|
|
||||||
inset=0;
|
|
||||||
}
|
|
||||||
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(16)+inset;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String formatDuration(Context context, int seconds){
|
public static String formatDuration(Context context, int seconds){
|
||||||
if(seconds<3600){
|
if(seconds<3600){
|
||||||
int minutes=seconds/60;
|
int minutes=seconds/60;
|
||||||
@@ -823,12 +783,4 @@ public class UiUtils{
|
|||||||
intent.putExtra(Intent.EXTRA_TEXT, url);
|
intent.putExtra(Intent.EXTRA_TEXT, url);
|
||||||
context.startActivity(Intent.createChooser(intent, context.getString(R.string.share_toot_title)));
|
context.startActivity(Intent.createChooser(intent, context.getString(R.string.share_toot_title)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void maybeShowTextCopiedToast(Context context){
|
|
||||||
//show toast, android from S_V2 on has built-in popup, as documented in
|
|
||||||
//https://developer.android.com/develop/ui/views/touch-and-input/copy-paste#duplicate-notifications
|
|
||||||
if(Build.VERSION.SDK_INT<=Build.VERSION_CODES.S_V2){
|
|
||||||
Toast.makeText(context, R.string.text_copied, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ public class ComposeMediaViewController{
|
|||||||
updateMediaAttachmentsLayout();
|
updateMediaAttachmentsLayout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean addMediaAttachment(Uri uri, String description){
|
public boolean addMediaAttachment(Uri uri, String description){
|
||||||
if(getMediaAttachmentsCount()==MAX_ATTACHMENTS){
|
if(getMediaAttachmentsCount()==MAX_ATTACHMENTS){
|
||||||
showMediaAttachmentError(fragment.getResources().getQuantityString(R.plurals.cant_add_more_than_x_attachments, MAX_ATTACHMENTS, MAX_ATTACHMENTS));
|
showMediaAttachmentError(fragment.getResources().getQuantityString(R.plurals.cant_add_more_than_x_attachments, MAX_ATTACHMENTS, MAX_ATTACHMENTS));
|
||||||
|
|||||||
@@ -1,190 +0,0 @@
|
|||||||
package org.joinmastodon.android.ui.viewcontrollers;
|
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.accessibility.AccessibilityNodeInfo;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
|
||||||
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.StringRes;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
|
||||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
|
||||||
import me.grishka.appkit.utils.V;
|
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
|
||||||
|
|
||||||
public abstract class DropdownSubmenuController{
|
|
||||||
protected List<Item<?>> items;
|
|
||||||
protected LinearLayout contentView;
|
|
||||||
protected UsableRecyclerView list;
|
|
||||||
protected TextView backItem;
|
|
||||||
protected final ToolbarDropdownMenuController dropdownController;
|
|
||||||
protected MergeRecyclerAdapter mergeAdapter;
|
|
||||||
protected ItemsAdapter itemsAdapter;
|
|
||||||
|
|
||||||
public DropdownSubmenuController(ToolbarDropdownMenuController dropdownController){
|
|
||||||
this.dropdownController=dropdownController;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract CharSequence getBackItemTitle();
|
|
||||||
public void onDismiss(){}
|
|
||||||
|
|
||||||
protected void createView(){
|
|
||||||
contentView=new LinearLayout(dropdownController.getActivity());
|
|
||||||
contentView.setOrientation(LinearLayout.VERTICAL);
|
|
||||||
CharSequence backTitle=getBackItemTitle();
|
|
||||||
if(!TextUtils.isEmpty(backTitle)){
|
|
||||||
backItem=(TextView) dropdownController.getActivity().getLayoutInflater().inflate(R.layout.item_dropdown_menu, contentView, false);
|
|
||||||
((LinearLayout.LayoutParams) backItem.getLayoutParams()).topMargin=V.dp(8);
|
|
||||||
backItem.setText(backTitle);
|
|
||||||
backItem.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_arrow_back, 0, 0, 0);
|
|
||||||
backItem.setBackground(UiUtils.getThemeDrawable(dropdownController.getActivity(), android.R.attr.selectableItemBackground));
|
|
||||||
backItem.setOnClickListener(v->dropdownController.popSubmenuController());
|
|
||||||
backItem.setAccessibilityDelegate(new View.AccessibilityDelegate(){
|
|
||||||
@Override
|
|
||||||
public void onInitializeAccessibilityNodeInfo(@NonNull View host, @NonNull AccessibilityNodeInfo info){
|
|
||||||
super.onInitializeAccessibilityNodeInfo(host, info);
|
|
||||||
info.setText(info.getText()+". "+host.getResources().getString(R.string.back));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
contentView.addView(backItem);
|
|
||||||
}
|
|
||||||
list=new UsableRecyclerView(dropdownController.getActivity());
|
|
||||||
list.setLayoutManager(new LinearLayoutManager(dropdownController.getActivity()));
|
|
||||||
itemsAdapter=new ItemsAdapter();
|
|
||||||
mergeAdapter=new MergeRecyclerAdapter();
|
|
||||||
mergeAdapter.addAdapter(itemsAdapter);
|
|
||||||
list.setAdapter(mergeAdapter);
|
|
||||||
list.setPadding(0, backItem!=null ? 0 : V.dp(8), 0, V.dp(8));
|
|
||||||
list.setClipToPadding(false);
|
|
||||||
list.setItemAnimator(new BetterItemAnimator());
|
|
||||||
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
|
||||||
private final Paint paint=new Paint();
|
|
||||||
{
|
|
||||||
paint.setStyle(Paint.Style.STROKE);
|
|
||||||
paint.setStrokeWidth(V.dp(1));
|
|
||||||
paint.setColor(UiUtils.getThemeColor(dropdownController.getActivity(), R.attr.colorM3OutlineVariant));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
|
||||||
for(int i=0;i<parent.getChildCount();i++){
|
|
||||||
View view=parent.getChildAt(i);
|
|
||||||
if(parent.getChildViewHolder(view) instanceof ItemHolder ih && ih.getItem().dividerBefore){
|
|
||||||
paint.setAlpha(Math.round(view.getAlpha()*255));
|
|
||||||
float y=view.getTop()-V.dp(8)-paint.getStrokeWidth()/2f;
|
|
||||||
c.drawLine(0, y, parent.getWidth(), y, paint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
|
||||||
if(parent.getChildViewHolder(view) instanceof ItemHolder ih && ih.getItem().dividerBefore){
|
|
||||||
outRect.top=V.dp(17);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
contentView.addView(list, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
|
||||||
}
|
|
||||||
|
|
||||||
public View getView(){
|
|
||||||
if(contentView==null)
|
|
||||||
createView();
|
|
||||||
return contentView;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected HideableSingleViewRecyclerAdapter createEmptyView(@DrawableRes int icon, @StringRes int title, @StringRes int subtitle){
|
|
||||||
View view=dropdownController.getActivity().getLayoutInflater().inflate(R.layout.popup_menu_empty, list, false);
|
|
||||||
ImageView iconView=view.findViewById(R.id.icon);
|
|
||||||
TextView titleView=view.findViewById(R.id.title);
|
|
||||||
TextView subtitleView=view.findViewById(R.id.subtitle);
|
|
||||||
iconView.setImageResource(icon);
|
|
||||||
titleView.setText(title);
|
|
||||||
subtitleView.setText(subtitle);
|
|
||||||
return new HideableSingleViewRecyclerAdapter(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final class Item<T>{
|
|
||||||
public final String title;
|
|
||||||
public final boolean hasSubmenu;
|
|
||||||
public final boolean dividerBefore;
|
|
||||||
public final T parentObject;
|
|
||||||
public final Consumer<Item<T>> onClick;
|
|
||||||
|
|
||||||
public Item(String title, boolean hasSubmenu, boolean dividerBefore, T parentObject, Consumer<Item<T>> onClick){
|
|
||||||
this.title=title;
|
|
||||||
this.hasSubmenu=hasSubmenu;
|
|
||||||
this.dividerBefore=dividerBefore;
|
|
||||||
this.parentObject=parentObject;
|
|
||||||
this.onClick=onClick;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Item(String title, boolean hasSubmenu, boolean dividerBefore, Consumer<Item<T>> onClick){
|
|
||||||
this(title, hasSubmenu, dividerBefore, null, onClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Item(@StringRes int titleRes, boolean hasSubmenu, boolean dividerBefore, Consumer<Item<T>> onClick){
|
|
||||||
this(dropdownController.getActivity().getString(titleRes), hasSubmenu, dividerBefore, null, onClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void performClick(){
|
|
||||||
onClick.accept(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected class ItemsAdapter extends RecyclerView.Adapter<ItemHolder>{
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public ItemHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
|
||||||
return new ItemHolder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull ItemHolder holder, int position){
|
|
||||||
holder.bind(items.get(position));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount(){
|
|
||||||
return items.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ItemHolder extends BindableViewHolder<Item<?>> implements UsableRecyclerView.Clickable{
|
|
||||||
private final TextView text;
|
|
||||||
|
|
||||||
public ItemHolder(){
|
|
||||||
super(dropdownController.getActivity(), R.layout.item_dropdown_menu, list);
|
|
||||||
text=(TextView) itemView;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBind(Item<?> item){
|
|
||||||
text.setText(item.title);
|
|
||||||
text.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, item.hasSubmenu ? R.drawable.ic_arrow_right_24px : 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(){
|
|
||||||
item.performClick();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
package org.joinmastodon.android.ui.viewcontrollers;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.Gravity;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.tags.GetFollowedTags;
|
|
||||||
import org.joinmastodon.android.fragments.ManageFollowedHashtagsFragment;
|
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
|
||||||
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import me.grishka.appkit.Nav;
|
|
||||||
import me.grishka.appkit.api.APIRequest;
|
|
||||||
import me.grishka.appkit.api.Callback;
|
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
|
||||||
import me.grishka.appkit.utils.V;
|
|
||||||
|
|
||||||
public class HomeTimelineHashtagsMenuController extends DropdownSubmenuController{
|
|
||||||
private HideableSingleViewRecyclerAdapter largeProgressAdapter;
|
|
||||||
private HideableSingleViewRecyclerAdapter emptyAdapter;
|
|
||||||
private APIRequest<?> currentRequest;
|
|
||||||
|
|
||||||
public HomeTimelineHashtagsMenuController(ToolbarDropdownMenuController dropdownController){
|
|
||||||
super(dropdownController);
|
|
||||||
items=new ArrayList<>();
|
|
||||||
loadHashtags();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void createView(){
|
|
||||||
super.createView();
|
|
||||||
emptyAdapter=createEmptyView(R.drawable.ic_tag_24px, R.string.no_followed_hashtags_title, R.string.no_followed_hashtags_subtitle);
|
|
||||||
FrameLayout largeProgressView=new FrameLayout(dropdownController.getActivity());
|
|
||||||
int pad=V.dp(32);
|
|
||||||
largeProgressView.setPadding(0, pad, 0, pad);
|
|
||||||
largeProgressView.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
|
||||||
ProgressBar progress=new ProgressBar(dropdownController.getActivity());
|
|
||||||
largeProgressView.addView(progress, new FrameLayout.LayoutParams(V.dp(48), V.dp(48), Gravity.CENTER));
|
|
||||||
largeProgressAdapter=new HideableSingleViewRecyclerAdapter(largeProgressView);
|
|
||||||
mergeAdapter.addAdapter(0, largeProgressAdapter);
|
|
||||||
emptyAdapter.setVisible(false);
|
|
||||||
mergeAdapter.addAdapter(0, emptyAdapter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected CharSequence getBackItemTitle(){
|
|
||||||
return dropdownController.getActivity().getString(R.string.followed_hashtags);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDismiss(){
|
|
||||||
if(currentRequest!=null){
|
|
||||||
currentRequest.cancel();
|
|
||||||
currentRequest=null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onTagClick(Item<Hashtag> item){
|
|
||||||
dropdownController.dismiss();
|
|
||||||
UiUtils.openHashtagTimeline(dropdownController.getActivity(), dropdownController.getAccountID(), item.parentObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onManageTagsClick(){
|
|
||||||
dropdownController.dismiss();
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", dropdownController.getAccountID());
|
|
||||||
Nav.go(dropdownController.getActivity(), ManageFollowedHashtagsFragment.class, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadHashtags(){
|
|
||||||
currentRequest=new GetFollowedTags(null, 200)
|
|
||||||
.setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(HeaderPaginationList<Hashtag> result){
|
|
||||||
currentRequest=null;
|
|
||||||
dropdownController.resizeOnNextFrame();
|
|
||||||
largeProgressAdapter.setVisible(false);
|
|
||||||
((List<Hashtag>) result).sort(Comparator.comparing(tag->tag.name));
|
|
||||||
int prevSize=items.size();
|
|
||||||
for(Hashtag tag:result){
|
|
||||||
items.add(new Item<>("#"+tag.name, false, false, tag, HomeTimelineHashtagsMenuController.this::onTagClick));
|
|
||||||
}
|
|
||||||
items.add(new Item<Void>(R.string.manage_hashtags, false, true, i->onManageTagsClick()));
|
|
||||||
itemsAdapter.notifyItemRangeInserted(prevSize, result.size()+1);
|
|
||||||
emptyAdapter.setVisible(result.isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
currentRequest=null;
|
|
||||||
Activity activity=dropdownController.getActivity();
|
|
||||||
if(activity!=null)
|
|
||||||
error.showToast(activity);
|
|
||||||
dropdownController.popSubmenuController();
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec(dropdownController.getAccountID());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package org.joinmastodon.android.ui.viewcontrollers;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.fragments.CreateListFragment;
|
|
||||||
import org.joinmastodon.android.fragments.ManageListsFragment;
|
|
||||||
import org.joinmastodon.android.model.FollowList;
|
|
||||||
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
|
||||||
|
|
||||||
public class HomeTimelineListsMenuController extends DropdownSubmenuController{
|
|
||||||
private final List<FollowList> lists;
|
|
||||||
private final HomeTimelineMenuController.Callback callback;
|
|
||||||
private HideableSingleViewRecyclerAdapter emptyAdapter;
|
|
||||||
|
|
||||||
public HomeTimelineListsMenuController(ToolbarDropdownMenuController dropdownController, HomeTimelineMenuController.Callback callback){
|
|
||||||
super(dropdownController);
|
|
||||||
this.lists=new ArrayList<>(callback.getLists());
|
|
||||||
this.callback=callback;
|
|
||||||
items=new ArrayList<>();
|
|
||||||
for(FollowList l:lists){
|
|
||||||
items.add(new Item<>(l.title, false, false, l, this::onListSelected));
|
|
||||||
}
|
|
||||||
items.add(new Item<Void>(dropdownController.getActivity().getString(R.string.create_list), false, true, i->{
|
|
||||||
dropdownController.dismiss();
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", dropdownController.getAccountID());
|
|
||||||
Nav.go(dropdownController.getActivity(), CreateListFragment.class, args);
|
|
||||||
}));
|
|
||||||
items.add(new Item<Void>(dropdownController.getActivity().getString(R.string.manage_lists), false, false, i->{
|
|
||||||
dropdownController.dismiss();
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", dropdownController.getAccountID());
|
|
||||||
Nav.go(dropdownController.getActivity(), ManageListsFragment.class, args);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected CharSequence getBackItemTitle(){
|
|
||||||
return dropdownController.getActivity().getString(R.string.lists);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void createView(){
|
|
||||||
super.createView();
|
|
||||||
emptyAdapter=createEmptyView(R.drawable.ic_list_alt_24px, R.string.no_lists_title, R.string.no_lists_subtitle);
|
|
||||||
if(lists.isEmpty()){
|
|
||||||
mergeAdapter.addAdapter(0, emptyAdapter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onListSelected(Item<FollowList> item){
|
|
||||||
callback.onListSelected(item.parentObject);
|
|
||||||
dropdownController.dismiss();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package org.joinmastodon.android.ui.viewcontrollers;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.model.FollowList;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class HomeTimelineMenuController extends DropdownSubmenuController{
|
|
||||||
private Callback callback;
|
|
||||||
|
|
||||||
public HomeTimelineMenuController(ToolbarDropdownMenuController dropdownController, Callback callback){
|
|
||||||
super(dropdownController);
|
|
||||||
this.callback=callback;
|
|
||||||
items=List.of(
|
|
||||||
new Item<Void>(R.string.timeline_following, false, false, i->{
|
|
||||||
callback.onFollowingSelected();
|
|
||||||
dropdownController.dismiss();
|
|
||||||
}),
|
|
||||||
new Item<Void>(R.string.local_timeline, false, false, i->{
|
|
||||||
callback.onLocalSelected();
|
|
||||||
dropdownController.dismiss();
|
|
||||||
}),
|
|
||||||
new Item<Void>(R.string.lists, true, true, i->dropdownController.pushSubmenuController(new HomeTimelineListsMenuController(dropdownController, callback))),
|
|
||||||
new Item<Void>(R.string.followed_hashtags, true, false, i->dropdownController.pushSubmenuController(new HomeTimelineHashtagsMenuController(dropdownController)))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected CharSequence getBackItemTitle(){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Callback{
|
|
||||||
void onFollowingSelected();
|
|
||||||
void onLocalSelected();
|
|
||||||
List<FollowList> getLists();
|
|
||||||
void onListSelected(FollowList list);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user