Compare commits
11 Commits
v2.7.3
...
v1.0.4-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12599db0ff | ||
|
|
c751c85c1c | ||
|
|
f1331a0f6d | ||
|
|
c75c9b60f9 | ||
|
|
eb3adf1dfd | ||
|
|
6533163fd0 | ||
|
|
1becad6016 | ||
|
|
d34653750e | ||
|
|
705592aefd | ||
|
|
583325d6e8 | ||
|
|
318d271127 |
13
README.md
13
README.md
@@ -1,12 +1,17 @@
|
|||||||
# Mastodon for Android
|
# Forked Mastodon for Android
|
||||||
[](https://crowdin.com/project/mastodon-for-android)
|
[](https://crowdin.com/project/mastodon-for-android)
|
||||||
|
|
||||||
<a href="https://play.google.com/store/apps/details?id=org.joinmastodon.android"><img src="img/google-play-badge.png" height="50"></a>
|
This is the repository for an officially forked Android app for Mastodon.
|
||||||
|
|
||||||
This is the repository for the official Android app for Mastodon.
|
|
||||||
|
|
||||||
Learn more about this app in the [blog post](https://blog.joinmastodon.org/2022/02/official-mastodon-for-android-app-is-coming-soon/).
|
Learn more about this app in the [blog post](https://blog.joinmastodon.org/2022/02/official-mastodon-for-android-app-is-coming-soon/).
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
* [Enable "Unlisted" as a visibility option](https://github.com/sk22/mastodon-android-fork/tree/feature/enable-unlisted)
|
||||||
|
([Pull request](https://github.com/mastodon/mastodon-android/pull/103)) and
|
||||||
|
[set as default](https://github.com/sk22/mastodon-android-fork/tree/feature/enable-unlisted-as-default)
|
||||||
|
* [Add "Federation" tab and change Discover tab order](https://github.com/sk22/mastodon-android-fork/tree/feature/add-federated-timeline)
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
As this app is using Java 17 features, you need JDK 17 or newer to build it. Other than that, everything is pretty standard. You can either import the project into Android Studio and build it from there, or run the following command in the project directory:
|
As this app is using Java 17 features, you need JDK 17 or newer to build it. Other than that, everything is pretty standard. You can either import the project into Android Studio and build it from there, or run the following command in the project directory:
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ android {
|
|||||||
applicationId "org.joinmastodon.android"
|
applicationId "org.joinmastodon.android"
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 31
|
targetSdk 31
|
||||||
versionCode 34
|
versionCode 3
|
||||||
versionName "1.0.4"
|
versionName '1.0.4-dev+fork.1.1'
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
private ImageView sendError;
|
private ImageView sendError;
|
||||||
private View sendingOverlay;
|
private View sendingOverlay;
|
||||||
private WindowManager wm;
|
private WindowManager wm;
|
||||||
private StatusPrivacy statusVisibility=StatusPrivacy.PUBLIC;
|
private StatusPrivacy statusVisibility=StatusPrivacy.UNLISTED;
|
||||||
private ComposeAutocompleteSpan currentAutocompleteSpan;
|
private ComposeAutocompleteSpan currentAutocompleteSpan;
|
||||||
private FrameLayout mainEditTextWrap;
|
private FrameLayout mainEditTextWrap;
|
||||||
private ComposeAutocompleteViewController autocompleteViewController;
|
private ComposeAutocompleteViewController autocompleteViewController;
|
||||||
@@ -1029,7 +1029,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
UiUtils.enablePopupMenuIcons(getActivity(), menu);
|
UiUtils.enablePopupMenuIcons(getActivity(), menu);
|
||||||
m.setGroupCheckable(0, true, true);
|
m.setGroupCheckable(0, true, true);
|
||||||
m.findItem(switch(statusVisibility){
|
m.findItem(switch(statusVisibility){
|
||||||
case PUBLIC, UNLISTED -> R.id.vis_public;
|
case PUBLIC -> R.id.vis_public;
|
||||||
|
case UNLISTED -> R.id.vis_unlisted;
|
||||||
case PRIVATE -> R.id.vis_followers;
|
case PRIVATE -> R.id.vis_followers;
|
||||||
case DIRECT -> R.id.vis_private;
|
case DIRECT -> R.id.vis_private;
|
||||||
}).setChecked(true);
|
}).setChecked(true);
|
||||||
@@ -1039,6 +1040,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
int id=item.getItemId();
|
int id=item.getItemId();
|
||||||
if(id==R.id.vis_public){
|
if(id==R.id.vis_public){
|
||||||
statusVisibility=StatusPrivacy.PUBLIC;
|
statusVisibility=StatusPrivacy.PUBLIC;
|
||||||
|
}else if(id==R.id.vis_unlisted){
|
||||||
|
statusVisibility=StatusPrivacy.UNLISTED;
|
||||||
}else if(id==R.id.vis_followers){
|
}else if(id==R.id.vis_followers){
|
||||||
statusVisibility=StatusPrivacy.PRIVATE;
|
statusVisibility=StatusPrivacy.PRIVATE;
|
||||||
}else if(id==R.id.vis_private){
|
}else if(id==R.id.vis_private){
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
private DiscoverAccountsFragment accountsFragment;
|
private DiscoverAccountsFragment accountsFragment;
|
||||||
private SearchFragment searchFragment;
|
private SearchFragment searchFragment;
|
||||||
private LocalTimelineFragment localTimelineFragment;
|
private LocalTimelineFragment localTimelineFragment;
|
||||||
|
private FederatedTimelineFragment federatedTimelineFragment;
|
||||||
|
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private Runnable searchDebouncer=this::onSearchChangedDebounced;
|
private Runnable searchDebouncer=this::onSearchChangedDebounced;
|
||||||
@@ -72,15 +73,16 @@ 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[5];
|
tabViews=new FrameLayout[6];
|
||||||
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_local_timeline;
|
||||||
case 1 -> R.id.discover_hashtags;
|
case 1 -> R.id.discover_federated_timeline;
|
||||||
case 2 -> R.id.discover_news;
|
case 2 -> R.id.discover_hashtags;
|
||||||
case 3 -> R.id.discover_local_timeline;
|
case 3 -> R.id.discover_posts;
|
||||||
case 4 -> R.id.discover_users;
|
case 4 -> R.id.discover_news;
|
||||||
|
case 5 -> 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);
|
||||||
@@ -106,7 +108,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if(postsFragment==null){
|
if(localTimelineFragment==null){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putBoolean("__is_tab", true);
|
args.putBoolean("__is_tab", true);
|
||||||
@@ -126,9 +128,13 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
localTimelineFragment=new LocalTimelineFragment();
|
localTimelineFragment=new LocalTimelineFragment();
|
||||||
localTimelineFragment.setArguments(args);
|
localTimelineFragment.setArguments(args);
|
||||||
|
|
||||||
|
federatedTimelineFragment=new FederatedTimelineFragment();
|
||||||
|
federatedTimelineFragment.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_local_timeline, localTimelineFragment)
|
||||||
|
.add(R.id.discover_federated_timeline, federatedTimelineFragment)
|
||||||
.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)
|
||||||
@@ -139,11 +145,12 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
@Override
|
@Override
|
||||||
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
|
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
|
||||||
tab.setText(switch(position){
|
tab.setText(switch(position){
|
||||||
case 0 -> R.string.posts;
|
case 0 -> R.string.local_timeline;
|
||||||
case 1 -> R.string.hashtags;
|
case 1 -> R.string.federated_timeline;
|
||||||
case 2 -> R.string.news;
|
case 2 -> R.string.hashtags;
|
||||||
case 3 -> R.string.local_timeline;
|
case 3 -> R.string.posts;
|
||||||
case 4 -> R.string.for_you;
|
case 4 -> R.string.news;
|
||||||
|
case 5 -> R.string.for_you;
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+position);
|
default -> throw new IllegalStateException("Unexpected value: "+position);
|
||||||
});
|
});
|
||||||
tab.view.textView.setAllCaps(true);
|
tab.view.textView.setAllCaps(true);
|
||||||
@@ -217,8 +224,8 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void loadData(){
|
public void loadData(){
|
||||||
if(postsFragment!=null && !postsFragment.loaded && !postsFragment.dataLoading)
|
if(localTimelineFragment!=null && !localTimelineFragment.loaded && !localTimelineFragment.dataLoading)
|
||||||
postsFragment.loadData();
|
localTimelineFragment.loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSearchEditFocusChanged(View v, boolean hasFocus){
|
private void onSearchEditFocusChanged(View v, boolean hasFocus){
|
||||||
@@ -254,11 +261,12 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
|
|
||||||
private Fragment getFragmentForPage(int page){
|
private Fragment getFragmentForPage(int page){
|
||||||
return switch(page){
|
return switch(page){
|
||||||
case 0 -> postsFragment;
|
case 0 -> localTimelineFragment;
|
||||||
case 1 -> hashtagsFragment;
|
case 1 -> federatedTimelineFragment;
|
||||||
case 2 -> newsFragment;
|
case 2 -> hashtagsFragment;
|
||||||
case 3 -> localTimelineFragment;
|
case 3 -> postsFragment;
|
||||||
case 4 -> accountsFragment;
|
case 4 -> newsFragment;
|
||||||
|
case 5 -> accountsFragment;
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
default -> throw new IllegalStateException("Unexpected value: "+page);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
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.fragments.StatusListFragment;
|
||||||
|
import org.joinmastodon.android.model.Filter;
|
||||||
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||||
|
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
|
||||||
|
public class FederatedTimelineFragment extends StatusListFragment{
|
||||||
|
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.FEDERATED_TIMELINE);
|
||||||
|
private String maxID;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doLoadData(int offset, int count){
|
||||||
|
currentRequest=new GetPublicTimeline(false, 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;
|
||||||
|
onDataLoaded(result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList()), !result.isEmpty());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
bannerHelper.maybeAddBanner(contentWrap);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,6 +36,7 @@ public class DiscoverInfoBannerHelper{
|
|||||||
case TRENDING_HASHTAGS -> R.string.trending_hashtags_info_banner;
|
case TRENDING_HASHTAGS -> R.string.trending_hashtags_info_banner;
|
||||||
case TRENDING_LINKS -> R.string.trending_links_info_banner;
|
case TRENDING_LINKS -> R.string.trending_links_info_banner;
|
||||||
case LOCAL_TIMELINE -> R.string.local_timeline_info_banner;
|
case LOCAL_TIMELINE -> R.string.local_timeline_info_banner;
|
||||||
|
case FEDERATED_TIMELINE -> R.string.federated_timeline_info_banner;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,6 +60,7 @@ public class DiscoverInfoBannerHelper{
|
|||||||
TRENDING_HASHTAGS,
|
TRENDING_HASHTAGS,
|
||||||
TRENDING_LINKS,
|
TRENDING_LINKS,
|
||||||
LOCAL_TIMELINE,
|
LOCAL_TIMELINE,
|
||||||
|
FEDERATED_TIMELINE,
|
||||||
// ACCOUNTS
|
// ACCOUNTS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
<item android:id="@+id/vis_public"
|
<item android:id="@+id/vis_public"
|
||||||
android:icon="@drawable/ic_fluent_earth_24_filled"
|
android:icon="@drawable/ic_fluent_earth_24_filled"
|
||||||
android:title="@string/visibility_public"/>
|
android:title="@string/visibility_public"/>
|
||||||
|
<item android:id="@+id/vis_unlisted"
|
||||||
|
android:icon="@drawable/ic_fluent_people_community_24_regular"
|
||||||
|
android:title="@string/visibility_unlisted"/>
|
||||||
<item android:id="@+id/vis_followers"
|
<item android:id="@+id/vis_followers"
|
||||||
android:icon="@drawable/ic_fluent_people_checkmark_24_regular"
|
android:icon="@drawable/ic_fluent_people_checkmark_24_regular"
|
||||||
android:title="@string/visibility_followers_only"/>
|
android:title="@string/visibility_followers_only"/>
|
||||||
|
|||||||
@@ -213,6 +213,7 @@
|
|||||||
<string name="alt_text_subtitle">Alternativtext erscheint für blinde Menschen. Versuche, nur so viele Details einzubeziehen, um den Kontext zu verstehen.</string>
|
<string name="alt_text_subtitle">Alternativtext erscheint für blinde Menschen. Versuche, nur so viele Details einzubeziehen, um den Kontext zu verstehen.</string>
|
||||||
<string name="alt_text_hint">z.B. Eine Giraffe auf einem Dreirad während sie eine Banane isst</string>
|
<string name="alt_text_hint">z.B. Eine Giraffe auf einem Dreirad während sie eine Banane isst</string>
|
||||||
<string name="visibility_public">Öffentlich</string>
|
<string name="visibility_public">Öffentlich</string>
|
||||||
|
<string name="visibility_unlisted">Nicht gelistet</string>
|
||||||
<string name="visibility_followers_only">Nur Folgende</string>
|
<string name="visibility_followers_only">Nur Folgende</string>
|
||||||
<string name="visibility_private">Nur Leute, die ich erwähne</string>
|
<string name="visibility_private">Nur Leute, die ich erwähne</string>
|
||||||
<string name="search_all">Alle</string>
|
<string name="search_all">Alle</string>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
<item name="discover_news" type="id"/>
|
<item name="discover_news" type="id"/>
|
||||||
<item name="discover_users" type="id"/>
|
<item name="discover_users" type="id"/>
|
||||||
<item name="discover_local_timeline" type="id"/>
|
<item name="discover_local_timeline" type="id"/>
|
||||||
|
<item name="discover_federated_timeline" type="id"/>
|
||||||
|
|
||||||
<item name="notifications_all" type="id"/>
|
<item name="notifications_all" type="id"/>
|
||||||
<item name="notifications_mentions" type="id"/>
|
<item name="notifications_mentions" type="id"/>
|
||||||
|
|||||||
@@ -218,6 +218,7 @@
|
|||||||
<string name="alt_text_subtitle">Alt text describes your photos for people with low or no vision. Try to only include enough detail to understand the context.</string>
|
<string name="alt_text_subtitle">Alt text describes your photos for people with low or no vision. Try to only include enough detail to understand the context.</string>
|
||||||
<string name="alt_text_hint">e.g. A dog looking around suspiciously with narrowed eyes at the camera.</string>
|
<string name="alt_text_hint">e.g. A dog looking around suspiciously with narrowed eyes at the camera.</string>
|
||||||
<string name="visibility_public">Public</string>
|
<string name="visibility_public">Public</string>
|
||||||
|
<string name="visibility_unlisted">Unlisted</string>
|
||||||
<string name="visibility_followers_only">Followers only</string>
|
<string name="visibility_followers_only">Followers only</string>
|
||||||
<string name="visibility_private">Only people I mention</string>
|
<string name="visibility_private">Only people I mention</string>
|
||||||
<string name="search_all">All</string>
|
<string name="search_all">All</string>
|
||||||
@@ -307,10 +308,12 @@
|
|||||||
<string name="downloading">Downloading…</string>
|
<string name="downloading">Downloading…</string>
|
||||||
<string name="no_app_to_handle_action">There\'s no app to handle this action</string>
|
<string name="no_app_to_handle_action">There\'s no app to handle this action</string>
|
||||||
<string name="local_timeline">Community</string>
|
<string name="local_timeline">Community</string>
|
||||||
|
<string name="federated_timeline">Federation</string>
|
||||||
<string name="trending_posts_info_banner">These are the posts gaining traction in your corner of Mastodon.</string>
|
<string name="trending_posts_info_banner">These are the posts gaining traction in your corner of Mastodon.</string>
|
||||||
<string name="trending_hashtags_info_banner">These are the hashtags gaining traction in your corner of Mastodon.</string>
|
<string name="trending_hashtags_info_banner">These are the hashtags gaining traction in your corner of Mastodon.</string>
|
||||||
<string name="trending_links_info_banner">These are the news stories being shared the most in your corner of Mastodon.</string>
|
<string name="trending_links_info_banner">These are the news stories being shared the most in your corner of Mastodon.</string>
|
||||||
<string name="local_timeline_info_banner">These are the most recent posts by the people who use the same Mastodon server as you.</string>
|
<string name="local_timeline_info_banner">These are the most recent posts by the people who use the same Mastodon server as you.</string>
|
||||||
|
<string name="federated_timeline_info_banner">These are the most recent posts by the people in your federation.</string>
|
||||||
<string name="dismiss">Dismiss</string>
|
<string name="dismiss">Dismiss</string>
|
||||||
<string name="see_new_posts">See new posts</string>
|
<string name="see_new_posts">See new posts</string>
|
||||||
<string name="load_missing_posts">Load missing posts</string>
|
<string name="load_missing_posts">Load missing posts</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user