Wrapstodon wip
This commit is contained in:
parent
48f9aabaf7
commit
1f9147624f
|
@ -9,7 +9,7 @@ android {
|
|||
applicationId "org.joinmastodon.android"
|
||||
minSdk 23
|
||||
targetSdk 33
|
||||
versionCode 82
|
||||
versionCode 83
|
||||
versionName "2.2.4"
|
||||
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"
|
||||
|
|
|
@ -84,6 +84,12 @@
|
|||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<provider
|
||||
android:authorities="${applicationId}.provider.wrapstodon_share"
|
||||
android:name=".WrapstodonImageProvider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true"/>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,74 @@
|
|||
package org.joinmastodon.android;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class WrapstodonImageProvider extends ContentProvider{
|
||||
@Override
|
||||
public boolean onCreate(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder){
|
||||
File image=new File(getContext().getCacheDir(), "wrapstodon.png");
|
||||
if(projection==null)
|
||||
projection=new String[]{"_display_name"};
|
||||
MatrixCursor cursor=new MatrixCursor(projection);
|
||||
if(!image.exists())
|
||||
return cursor;
|
||||
Object[] values=new Object[projection.length];
|
||||
for(int i=0;i<projection.length;i++){
|
||||
if("_display_name".equals(projection[i]))
|
||||
values[i]="wrapstodon.png";
|
||||
if("_size".equals(projection[i]))
|
||||
values[i]=image.length();
|
||||
}
|
||||
cursor.addRow(values);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getType(@NonNull Uri uri){
|
||||
return "image/png";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values){
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs){
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs){
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException{
|
||||
if(!"r".equals(mode))
|
||||
throw new FileNotFoundException("Unsupported mode");
|
||||
File image=new File(getContext().getCacheDir(), "wrapstodon.png");
|
||||
if(!image.exists())
|
||||
throw new FileNotFoundException();
|
||||
return ParcelFileDescriptor.open(image, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package org.joinmastodon.android.api.requests.annual_reports;
|
||||
|
||||
import org.joinmastodon.android.api.AllFieldsAreRequired;
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.api.ObjectValidationException;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.AnnualReport;
|
||||
import org.joinmastodon.android.model.BaseModel;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GetAnnualReports extends MastodonAPIRequest<GetAnnualReports.Response>{
|
||||
|
||||
public GetAnnualReports(){
|
||||
super(HttpMethod.GET, "/annual_reports", Response.class);
|
||||
}
|
||||
|
||||
@AllFieldsAreRequired
|
||||
public static class Response extends BaseModel{
|
||||
public List<AnnualReportYear> annualReports;
|
||||
public List<Account> accounts;
|
||||
public List<Status> statuses;
|
||||
|
||||
@Override
|
||||
public void postprocess() throws ObjectValidationException{
|
||||
super.postprocess();
|
||||
for(AnnualReportYear r:annualReports){
|
||||
if(r.data==null)
|
||||
throw new ObjectValidationException("data is null");
|
||||
r.data.postprocess();
|
||||
}
|
||||
for(Account a:accounts)
|
||||
a.postprocess();
|
||||
for(Status s:statuses)
|
||||
s.postprocess();
|
||||
}
|
||||
|
||||
public static class AnnualReportYear{
|
||||
public int year;
|
||||
public AnnualReport data;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ import android.animation.AnimatorListenerAdapter;
|
|||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.app.Activity;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
|
@ -41,9 +42,12 @@ import org.joinmastodon.android.model.FilterContext;
|
|||
import org.joinmastodon.android.model.FollowList;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.TimelineMarkers;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
|
||||
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;
|
||||
|
@ -81,6 +85,7 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||
private FollowList currentList;
|
||||
private MergeRecyclerAdapter mergeAdapter;
|
||||
private DiscoverInfoBannerHelper localTimelineBannerHelper;
|
||||
private HideableSingleViewRecyclerAdapter yearlyWrapBannerAdapter;
|
||||
|
||||
private String maxID;
|
||||
private String lastSavedMarkerID;
|
||||
|
@ -635,7 +640,11 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||
|
||||
@Override
|
||||
protected RecyclerView.Adapter getAdapter(){
|
||||
yearlyWrapBannerAdapter=new HideableSingleViewRecyclerAdapter(makeWrapstodonBannerView());
|
||||
// TODO only show banner when some condition is met (TBD)
|
||||
|
||||
mergeAdapter=new MergeRecyclerAdapter();
|
||||
mergeAdapter.addAdapter(yearlyWrapBannerAdapter);
|
||||
mergeAdapter.addAdapter(super.getAdapter());
|
||||
return mergeAdapter;
|
||||
}
|
||||
|
@ -661,6 +670,43 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||
};
|
||||
}
|
||||
|
||||
private View makeWrapstodonBannerView(){
|
||||
int bgColor, accentColor, iconContainerColor, textColor;
|
||||
accentColor=0xFFBAFF3B;
|
||||
bgColor=0xFF2F0C7A;
|
||||
iconContainerColor=0xFF563ACC;
|
||||
if(UiUtils.isDarkTheme()){
|
||||
textColor=0xFFEEE5FF;
|
||||
}else{
|
||||
textColor=0xFFCCCFFF;
|
||||
}
|
||||
View banner=getActivity().getLayoutInflater().inflate(R.layout.item_settings_banner, list, false);
|
||||
TextView bannerText=banner.findViewById(R.id.text);
|
||||
TextView bannerTitle=banner.findViewById(R.id.title);
|
||||
ImageView bannerIcon=banner.findViewById(R.id.icon);
|
||||
Button bannerButton=banner.findViewById(R.id.button);
|
||||
banner.findViewById(R.id.button2).setVisibility(View.GONE);
|
||||
bannerTitle.setText(getString(R.string.yearly_wrap_title, "2023"));
|
||||
bannerText.setText(R.string.yearly_wrap_text);
|
||||
bannerButton.setText(R.string.yearly_wrap_view);
|
||||
bannerIcon.setImageResource(R.drawable.ic_celebration_24px);
|
||||
banner.setBackgroundColor(bgColor);
|
||||
bannerText.setTextColor(textColor);
|
||||
bannerTitle.setTextColor(textColor);
|
||||
bannerIcon.setBackgroundTintList(ColorStateList.valueOf(iconContainerColor));
|
||||
bannerIcon.setImageTintList(ColorStateList.valueOf(accentColor));
|
||||
bannerButton.setTextColor(accentColor);
|
||||
bannerButton.setBackgroundTintList(ColorStateList.valueOf(accentColor));
|
||||
bannerButton.setOnClickListener(v->{
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.go(getActivity(), WrapstodonFragment.class, args);
|
||||
});
|
||||
banner.setOutlineProvider(OutlineProviders.roundedRect(12));
|
||||
banner.setClipToOutline(true);
|
||||
return banner;
|
||||
}
|
||||
|
||||
private enum ListMode{
|
||||
FOLLOWING,
|
||||
LOCAL,
|
||||
|
|
|
@ -0,0 +1,322 @@
|
|||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.api.requests.annual_reports.GetAnnualReports;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.AnnualReport;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.wrapstodon.AppsWrapScene;
|
||||
import org.joinmastodon.android.ui.wrapstodon.ArchetypeWrapScene;
|
||||
import org.joinmastodon.android.ui.wrapstodon.ByTheNumbersWrapScene;
|
||||
import org.joinmastodon.android.ui.wrapstodon.FavoriteAccountsWrapScene;
|
||||
import org.joinmastodon.android.ui.wrapstodon.FavoriteHashtagsWrapScene;
|
||||
import org.joinmastodon.android.ui.wrapstodon.InteractedAccountsWrapScene;
|
||||
import org.joinmastodon.android.ui.wrapstodon.SummaryWrapScene;
|
||||
import org.joinmastodon.android.ui.wrapstodon.TimeSeriesWrapScene;
|
||||
import org.joinmastodon.android.ui.wrapstodon.WelcomeWrapScene;
|
||||
import org.joinmastodon.android.ui.wrapstodon.AnnualWrapScene;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.AppKitFragment;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class WrapstodonFragment extends AppKitFragment{
|
||||
private boolean statusBarHidden;
|
||||
private FrameLayout contentWrap;
|
||||
private ViewPager2 pager;
|
||||
private ProgressBar progress;
|
||||
private FrameLayout innerWrap;
|
||||
private boolean dataLoaded;
|
||||
private String accountID;
|
||||
|
||||
private List<AnnualWrapScene> scenes=List.of();
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
accountID=getArguments().getString("account");
|
||||
if(BuildConfig.DEBUG && new File(getActivity().getFilesDir(), "mockAnnualReports.json").exists()){
|
||||
try(FileReader reader=new FileReader(new File(getActivity().getFilesDir(), "mockAnnualReports.json"))){
|
||||
GetAnnualReports.Response res=MastodonAPIController.gson.fromJson(reader, GetAnnualReports.Response.class);
|
||||
showReport(res);
|
||||
}catch(IOException x){
|
||||
throw new RuntimeException(x);
|
||||
}
|
||||
}else{
|
||||
new GetAnnualReports()
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(GetAnnualReports.Response result){
|
||||
showReport(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
Activity a=getActivity();
|
||||
if(a==null)
|
||||
return;
|
||||
error.showToast(a);
|
||||
Nav.finish(WrapstodonFragment.this);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
}
|
||||
|
||||
private void showReport(GetAnnualReports.Response result){
|
||||
if(result.annualReports.isEmpty()){
|
||||
Nav.finish(WrapstodonFragment.this);
|
||||
return;
|
||||
}
|
||||
dataLoaded=true;
|
||||
if(progress!=null){
|
||||
V.setVisibilityAnimated(progress, View.GONE);
|
||||
V.setVisibilityAnimated(innerWrap, View.VISIBLE);
|
||||
}
|
||||
AccountSession session=AccountSessionManager.get(accountID);
|
||||
|
||||
String year=String.valueOf(result.annualReports.get(result.annualReports.size()-1).year);
|
||||
AnnualReport report=result.annualReports.get(result.annualReports.size()-1).data;
|
||||
Map<String, Account> accounts=result.accounts.stream().collect(Collectors.toMap(a->a.id, Function.identity(), (a1, a2)->a2));
|
||||
Map<String, Status> statuses=result.statuses.stream().collect(Collectors.toMap(s->s.id, Function.identity(), (s1, s2)->s2));
|
||||
|
||||
scenes=List.of(
|
||||
new WelcomeWrapScene(),
|
||||
new ArchetypeWrapScene(session.self.username, session.domain, session.self.avatarStatic, report.archetype),
|
||||
new ByTheNumbersWrapScene(report.typeDistribution),
|
||||
new FavoriteAccountsWrapScene(accounts, report.mostRebloggedAccounts),
|
||||
new FavoriteHashtagsWrapScene(report.topHashtags),
|
||||
new InteractedAccountsWrapScene(session.self, accounts, report.commonlyInteractedWithAccounts),
|
||||
new AppsWrapScene(report.mostUsedApps),
|
||||
new TimeSeriesWrapScene(report.timeSeries),
|
||||
new SummaryWrapScene(session.self, accounts, statuses, report)
|
||||
);
|
||||
|
||||
for(AnnualWrapScene scene:scenes){
|
||||
scene.setYear(year);
|
||||
}
|
||||
|
||||
if(pager!=null)
|
||||
pager.getAdapter().notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
|
||||
contentWrap=new ContentWrapFrameLayout(getActivity());
|
||||
contentWrap.setBackgroundColor(0xff000000); // TODO optimize overdraw
|
||||
|
||||
progress=new ProgressBar(getActivity());
|
||||
progress.setIndeterminateTintList(ColorStateList.valueOf(0xffffffff));
|
||||
contentWrap.addView(progress, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
|
||||
|
||||
innerWrap=new InnerWrapFrameLayout(getActivity());
|
||||
innerWrap.setBackgroundColor(0xFF17063B);
|
||||
innerWrap.setOutlineProvider(OutlineProviders.roundedRect(8));
|
||||
innerWrap.setClipToOutline(true);
|
||||
contentWrap.addView(innerWrap, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
|
||||
|
||||
pager=new ViewPager2(getActivity());
|
||||
pager.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
|
||||
pager.setAdapter(new ScenesAdapter());
|
||||
pager.setOffscreenPageLimit(1);
|
||||
innerWrap.addView(pager, new FrameLayout.LayoutParams(V.dp(360), V.dp(640), Gravity.CENTER));
|
||||
|
||||
ContextThemeWrapper themeContext=new ContextThemeWrapper(getActivity(), R.style.Theme_Mastodon_Dark);
|
||||
LayoutInflater.from(themeContext).inflate(R.layout.wrap_top_bar, innerWrap);
|
||||
innerWrap.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
|
||||
Button shareButton=innerWrap.findViewById(R.id.btn_share);
|
||||
shareButton.setOnClickListener(v->shareCurrentScene());
|
||||
shareButton.setVisibility(View.INVISIBLE);
|
||||
|
||||
if(dataLoaded)
|
||||
progress.setVisibility(View.GONE);
|
||||
else
|
||||
innerWrap.setVisibility(View.INVISIBLE);
|
||||
|
||||
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
||||
private boolean isFirst=true;
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position){
|
||||
if(isFirst!=(position==0)){
|
||||
isFirst=position==0;
|
||||
V.setVisibilityAnimated(shareButton, isFirst ? View.INVISIBLE : View.VISIBLE);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return contentWrap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShown(){
|
||||
super.onShown();
|
||||
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHidden(){
|
||||
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
|
||||
super.onHidden();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyWindowInsets(WindowInsets insets){
|
||||
super.onApplyWindowInsets(insets);
|
||||
contentWrap.setPadding(0, statusBarHidden ? 0 : insets.getSystemWindowInsetTop(), 0, insets.getSystemWindowInsetBottom());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wantsLightNavigationBar(){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wantsLightStatusBar(){
|
||||
return false;
|
||||
}
|
||||
|
||||
private void shareCurrentScene(){
|
||||
Bitmap bmp=scenes.get(pager.getCurrentItem()).renderToBitmap();
|
||||
try(FileOutputStream out=new FileOutputStream(new File(getActivity().getCacheDir(), "wrapstodon.png"))){
|
||||
bmp.compress(Bitmap.CompressFormat.PNG, 100, out);
|
||||
Intent intent=new Intent(Intent.ACTION_SEND);
|
||||
intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://"+getActivity().getPackageName()+".provider.wrapstodon_share/wrapstodon.png"));
|
||||
intent.setType("image/png");
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
startActivity(Intent.createChooser(intent, getString(R.string.share_toot_title)));
|
||||
}catch(IOException x){
|
||||
Toast.makeText(getActivity(), R.string.error_saving_file, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private class ContentWrapFrameLayout extends FrameLayout{
|
||||
public ContentWrapFrameLayout(@NonNull Context context){
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh){
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
post(this::updateStatusBarVisibility);
|
||||
}
|
||||
|
||||
private void updateStatusBarVisibility(){
|
||||
float aspect=(float)getWidth()/(getHeight()-getPaddingBottom());
|
||||
// Hide the status bar if the screen is 9:16 or wider
|
||||
if(aspect>=0.5625f){
|
||||
setSystemUiVisibility(getSystemUiVisibility() | SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
|
||||
statusBarHidden=true;
|
||||
}else{
|
||||
setSystemUiVisibility(getSystemUiVisibility() & ~(SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_IMMERSIVE_STICKY));
|
||||
statusBarHidden=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class InnerWrapFrameLayout extends FrameLayout{
|
||||
public InnerWrapFrameLayout(@NonNull Context context){
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
|
||||
int w=MeasureSpec.getSize(widthMeasureSpec);
|
||||
int h=MeasureSpec.getSize(heightMeasureSpec);
|
||||
int mw, mh;
|
||||
if(w>h){
|
||||
mw=Math.round(h*0.5626f);
|
||||
mh=h;
|
||||
}else{
|
||||
float aspect=(float)w/h;
|
||||
if(aspect<0.5625f){ // 9:16 or taller
|
||||
mw=w;
|
||||
mh=Math.round(w/0.5625f);
|
||||
}else if(aspect<0.62){ // special case for 9:16 screens with a navigation bar
|
||||
mw=w;
|
||||
mh=h;
|
||||
}else{
|
||||
mw=Math.round(h*0.5626f);
|
||||
mh=h;
|
||||
}
|
||||
}
|
||||
float contentScaleFactor=(float)mw/V.dp(360);
|
||||
View child0=getChildAt(0);
|
||||
child0.getLayoutParams().height=Math.round(mh/contentScaleFactor);
|
||||
child0.setScaleX(contentScaleFactor);
|
||||
child0.setScaleY(contentScaleFactor);
|
||||
super.onMeasure(mw | MeasureSpec.EXACTLY, mh | MeasureSpec.EXACTLY);
|
||||
}
|
||||
}
|
||||
|
||||
private class ScenesAdapter extends RecyclerView.Adapter<SceneViewHolder>{
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public SceneViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new SceneViewHolder(scenes.get(viewType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull SceneViewHolder holder, int position){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return scenes.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position){
|
||||
return position;
|
||||
}
|
||||
}
|
||||
|
||||
private class SceneViewHolder extends RecyclerView.ViewHolder{
|
||||
public SceneViewHolder(AnnualWrapScene scene){
|
||||
super(scene.createContentView(getActivity()));
|
||||
itemView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package org.joinmastodon.android.model;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class AnnualReport extends BaseModel{
|
||||
public Archetype archetype;
|
||||
public Map<String, Double> percentiles;
|
||||
public List<TimeSeriesPoint> timeSeries;
|
||||
public List<NameAndCount> topHashtags;
|
||||
public TopStatuses topStatuses;
|
||||
public List<NameAndCount> mostUsedApps;
|
||||
public TypeDistribution typeDistribution;
|
||||
public List<AccountAndCount> mostRebloggedAccounts;
|
||||
public List<AccountAndCount> commonlyInteractedWithAccounts;
|
||||
|
||||
public enum Archetype{
|
||||
@SerializedName("lurker")
|
||||
LURKER,
|
||||
@SerializedName("booster")
|
||||
BOOSTER,
|
||||
@SerializedName("replier")
|
||||
REPLIER,
|
||||
@SerializedName("pollster")
|
||||
POLLSTER,
|
||||
@SerializedName("oracle")
|
||||
ORACLE
|
||||
}
|
||||
|
||||
public static class TimeSeriesPoint{
|
||||
public int month;
|
||||
public int statuses;
|
||||
public int followers;
|
||||
public int following;
|
||||
}
|
||||
|
||||
public static class NameAndCount{
|
||||
public String name;
|
||||
public int count;
|
||||
}
|
||||
|
||||
public static class TopStatuses{
|
||||
public String byReblogs;
|
||||
public String byReplies;
|
||||
public String byFavourites;
|
||||
}
|
||||
|
||||
public static class TypeDistribution{
|
||||
public int total;
|
||||
public int reblogs;
|
||||
public int replies;
|
||||
public int standalone;
|
||||
}
|
||||
|
||||
public static class AccountAndCount{
|
||||
public String accountId;
|
||||
public int count;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
package org.joinmastodon.android.ui.drawables;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class InnerShadowDrawable extends Drawable{
|
||||
private int radius, color, offsetX, offsetY, cornerRadius;
|
||||
private Path path=new Path();
|
||||
private Paint shadowPaint=new Paint(Paint.ANTI_ALIAS_FLAG), bitmapShadowPaint=new Paint(Paint.ANTI_ALIAS_FLAG), bitmapPaint=new Paint();
|
||||
private RectF tmpRect=new RectF();
|
||||
private Bitmap bitmap;
|
||||
|
||||
public InnerShadowDrawable(int cornerRadius, int blurRadius, int color, int offsetX, int offsetY){
|
||||
this.radius=blurRadius;
|
||||
this.color=color;
|
||||
this.offsetX=offsetX;
|
||||
this.offsetY=offsetY;
|
||||
this.cornerRadius=cornerRadius;
|
||||
shadowPaint.setColor(color);
|
||||
shadowPaint.setShadowLayer(blurRadius, offsetX, offsetY, color | 0xFF000000);
|
||||
bitmapPaint.setColor(color);
|
||||
bitmapShadowPaint.setColor(0xFF000000);
|
||||
bitmapShadowPaint.setShadowLayer(blurRadius, offsetX, offsetY, 0xFF000000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas){
|
||||
if(Build.VERSION.SDK_INT>=28 || !canvas.isHardwareAccelerated()){
|
||||
canvas.drawPath(path, shadowPaint);
|
||||
}else{
|
||||
if(bitmap==null){
|
||||
bitmap=Bitmap.createBitmap(getBounds().width(), getBounds().height(), Bitmap.Config.ALPHA_8);
|
||||
new Canvas(bitmap).drawPath(path, bitmapShadowPaint);
|
||||
}
|
||||
canvas.drawBitmap(bitmap, getBounds().left, getBounds().top, bitmapPaint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable ColorFilter colorFilter){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity(){
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(@NonNull Rect bounds){
|
||||
super.onBoundsChange(bounds);
|
||||
updatePath();
|
||||
}
|
||||
|
||||
private void updatePath(){
|
||||
path.rewind();
|
||||
tmpRect.set(getBounds());
|
||||
tmpRect.inset(-100, -100);
|
||||
path.addRect(tmpRect, Path.Direction.CW);
|
||||
tmpRect.set(getBounds());
|
||||
path.addRoundRect(tmpRect, cornerRadius, cornerRadius, Path.Direction.CCW);
|
||||
bitmap=null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package org.joinmastodon.android.ui.utils;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
public class NestedScrollingTouchDisallower implements View.OnTouchListener{
|
||||
private float downY, touchslop;
|
||||
private boolean didDisallow;
|
||||
|
||||
public NestedScrollingTouchDisallower(View v){
|
||||
touchslop=ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent ev){
|
||||
if(ev.getAction()==MotionEvent.ACTION_DOWN){
|
||||
if(v.canScrollVertically(-1) || v.canScrollVertically(1)){
|
||||
v.getParent().requestDisallowInterceptTouchEvent(true);
|
||||
didDisallow=true;
|
||||
}else{
|
||||
didDisallow=false;
|
||||
}
|
||||
downY=ev.getY();
|
||||
}else if(didDisallow && ev.getAction()==MotionEvent.ACTION_MOVE){
|
||||
if(Math.abs(downY-ev.getY())>=touchslop){
|
||||
if(!v.canScrollVertically((int)(downY-ev.getY()))){
|
||||
didDisallow=false;
|
||||
v.getParent().requestDisallowInterceptTouchEvent(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@ package org.joinmastodon.android.ui.views;
|
|||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.widget.ScrollView;
|
||||
|
@ -43,6 +42,7 @@ public class NestableScrollView extends ScrollView{
|
|||
didDisallow=true;
|
||||
}else{
|
||||
didDisallow=false;
|
||||
return false;
|
||||
}
|
||||
downY=ev.getY();
|
||||
}else if(didDisallow && ev.getAction()==MotionEvent.ACTION_MOVE){
|
||||
|
@ -50,6 +50,7 @@ public class NestableScrollView extends ScrollView{
|
|||
if(!canScrollVertically((int)(downY-ev.getY()))){
|
||||
didDisallow=false;
|
||||
getParent().requestDisallowInterceptTouchEvent(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
package org.joinmastodon.android.ui.wrapstodon;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Note: all scenes are rendered in a fixed-size viewport of 360*640dp, then scaled as necessary to fit the screen.
|
||||
* Works well on common phone screen sizes, might be kinda meh on tablets.
|
||||
*/
|
||||
public abstract class AnnualWrapScene{
|
||||
protected View contentView;
|
||||
protected String year;
|
||||
|
||||
protected abstract View onCreateContentView(Context context);
|
||||
protected abstract void onDestroyContentView();
|
||||
|
||||
public View createContentView(Context context){
|
||||
if(contentView!=null)
|
||||
return contentView;
|
||||
return contentView=onCreateContentView(context);
|
||||
}
|
||||
|
||||
public void destroyContentView(){
|
||||
onDestroyContentView();
|
||||
contentView=null;
|
||||
}
|
||||
|
||||
protected View getViewForScreenshot(){
|
||||
return contentView;
|
||||
}
|
||||
|
||||
public Bitmap renderToBitmap(){
|
||||
if(contentView==null)
|
||||
throw new IllegalStateException();
|
||||
View v=getViewForScreenshot();
|
||||
Bitmap bmp=Bitmap.createBitmap(1080, Math.round(1080/(float)v.getWidth()*v.getHeight()), Bitmap.Config.ARGB_8888);
|
||||
Canvas c=new Canvas(bmp);
|
||||
c.drawColor(0xFF17063B);
|
||||
float scale=3f/v.getResources().getDisplayMetrics().density;
|
||||
c.scale(scale, scale, 0, 0);
|
||||
v.draw(c);
|
||||
return bmp;
|
||||
}
|
||||
|
||||
public void setYear(String year){
|
||||
this.year=year;
|
||||
}
|
||||
|
||||
protected CharSequence replaceBoldWithColor(CharSequence src, int color){
|
||||
Spannable ssb;
|
||||
if(src instanceof Spannable s){
|
||||
ssb=s;
|
||||
}else{
|
||||
ssb=new SpannableString(src);
|
||||
}
|
||||
|
||||
StyleSpan[] spans=ssb.getSpans(0, ssb.length(), StyleSpan.class);
|
||||
for(StyleSpan span:spans){
|
||||
int start=ssb.getSpanStart(span);
|
||||
int end=ssb.getSpanEnd(span);
|
||||
ssb.removeSpan(span);
|
||||
ssb.setSpan(new ForegroundColorSpan(color), start, end, 0);
|
||||
}
|
||||
|
||||
return ssb;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
package org.joinmastodon.android.ui.wrapstodon;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.AnnualReport;
|
||||
import org.joinmastodon.android.ui.drawables.TiledDrawable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class AppsWrapScene extends AnnualWrapScene{
|
||||
private final List<AnnualReport.NameAndCount> appStats;
|
||||
|
||||
public AppsWrapScene(List<AnnualReport.NameAndCount> appStats){
|
||||
this.appStats=appStats;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View onCreateContentView(Context context){
|
||||
LayoutInflater inflater=LayoutInflater.from(context);
|
||||
View content=inflater.inflate(R.layout.wrap_apps, null);
|
||||
content.setBackground(new TiledDrawable(context.getResources().getDrawable(R.drawable.chiclet_pattern, context.getTheme())));
|
||||
|
||||
View[] appBars={
|
||||
content.findViewById(R.id.app1_bar),
|
||||
content.findViewById(R.id.app2_bar),
|
||||
content.findViewById(R.id.app3_bar)
|
||||
};
|
||||
ImageView[] appIcons={
|
||||
content.findViewById(R.id.app1_icon),
|
||||
content.findViewById(R.id.app2_icon),
|
||||
content.findViewById(R.id.app3_icon)
|
||||
};
|
||||
TextView[] appNames={
|
||||
content.findViewById(R.id.app1_name),
|
||||
content.findViewById(R.id.app2_name),
|
||||
content.findViewById(R.id.app3_name)
|
||||
};
|
||||
|
||||
int max=appStats.get(0).count;
|
||||
int i=0;
|
||||
for(AnnualReport.NameAndCount app:appStats){
|
||||
appNames[i].setText(app.name);
|
||||
appBars[i].setTranslationY(V.dp(250)*(1-(app.count/(float)max)));
|
||||
if("Mastodon for Android".equals(app.name) || "Mastodon for iOS".equals(app.name)){
|
||||
appIcons[i].setImageResource(R.mipmap.ic_launcher);
|
||||
}else{
|
||||
String url=getIconUrl(app.name);
|
||||
if(url==null)
|
||||
url=getIconUrl(app.name.split(" ")[0]);
|
||||
if(url!=null){
|
||||
ViewImageLoader.loadWithoutAnimation(appIcons[i], null, new UrlImageLoaderRequest(Bitmap.Config.ARGB_8888, V.dp(96), V.dp(96), List.of(), Uri.parse(url)));
|
||||
}
|
||||
}
|
||||
i++;
|
||||
if(i==3)
|
||||
break;
|
||||
}
|
||||
if(i<3){
|
||||
for(int j=i;j<3;j++)
|
||||
appBars[j].setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroyContentView(){
|
||||
|
||||
}
|
||||
|
||||
static String getIconUrl(String name){
|
||||
return switch(name){
|
||||
case "Web" -> "https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Netscape_icon.svg/128px-Netscape_icon.svg.png";
|
||||
|
||||
case "Rodent" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Frodent.d6a6e73b.png&w=1080&q=75";
|
||||
case "Focus" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ffocus.5d3f5755.png&w=1080&q=75";
|
||||
case "Tusky" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftusky.28fb3514.png&w=384&q=75";
|
||||
case "Subway Tooter" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fsubway-tooter.165ce486.png&w=384&q=75";
|
||||
case "Fedilab" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ffedilab.8fce088e.png&w=384&q=75";
|
||||
case "Megalodon" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fmegalodon.d1fd421a.png&w=384&q=75";
|
||||
case "Moshidon" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fmoshidon.d0d53493.png&w=384&q=75";
|
||||
case "ZonePane" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fzonepane.add34ea8.png&w=1080&q=75";
|
||||
case "Pachli" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fpachli.0a3fda9d.png&w=1080&q=75";
|
||||
case "Toot!" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftoot.9fce2178.jpg&w=640&q=75";
|
||||
case "Mast" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fmast.17193b21.png&w=640&q=75";
|
||||
case "iMast" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimast_icon.9713d97a.png&w=1080&q=75";
|
||||
case "Ice Cubes" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ficecubes.141ad567.png&w=828&q=75";
|
||||
case "Ivory" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fivory.3878bb47.png&w=1080&q=75";
|
||||
case "Mammoth" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fmammoth.19d0726e.png&w=828&q=75";
|
||||
case "Woolly" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fwoolly.270ab54a.png&w=1080&q=75";
|
||||
case "DAWN for Mastodon" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdawn.5a728ea4.png&w=1080&q=75";
|
||||
case "Mona" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fmona.962904ab.png&w=1080&q=75";
|
||||
case "Radiant" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fradiant.88eaaf27.png&w=1080&q=75";
|
||||
case "TootDesk" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftootdesk.089737e3.png&w=1080&q=75";
|
||||
case "Stomp (watchOS)" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fstomp.ac7757e7.png&w=2048&q=75";
|
||||
case "feather" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ffeather.448a23c4.png&w=2048&q=75";
|
||||
case "SoraSNS" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fsora.aa83aab1.png&w=1200&q=75";
|
||||
case "Pipilo" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fpipilo.9d0314d1.png&w=1080&q=75";
|
||||
case "Pinafore" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fpinafore.ba6b1933.png&w=256&q=75";
|
||||
case "Elk" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Felk.db28f01a.png&w=384&q=75";
|
||||
case "Buffer" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fbuffer.1b722f8e.png&w=1080&q=75";
|
||||
case "Statuzer" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fstatuzer.04c7fa8a.png&w=384&q=75";
|
||||
case "Fedica" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ffedica.a2b32162.png&w=2048&q=75";
|
||||
case "Phanpy" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fphanpy.97a4a3c1.png&w=384&q=75";
|
||||
case "Trunks" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftrunks.1e0e665e.png&w=1080&q=75";
|
||||
case "Litterbox" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flitterbox.f4015748.png&w=640&q=75";
|
||||
case "Tooty" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftooty.f8664e1a.png&w=640&q=75";
|
||||
case "Mastodeck" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fmastodeck.985ab21b.png&w=640&q=75";
|
||||
case "Tokodon" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftokodon.ba8f924d.png&w=640&q=75";
|
||||
case "Whalebird" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fwhalebird.cd50e388.png&w=256&q=75";
|
||||
case "TheDesk" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fthedesk.1cd41d27.png&w=1080&q=75";
|
||||
case "Hyperspace" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fhyperspace.668fa418.png&w=256&q=75";
|
||||
case "Mastonaut" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fmastonaut.c026dca5.png&w=640&q=75";
|
||||
case "Sengi" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fsengi.c16fc152.png&w=828&q=75";
|
||||
case "Bitlbee-Mastodon" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fbitlbee.35ddf1a4.png&w=384&q=75";
|
||||
case "Tuba" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftuba.adee2feb.png&w=640&q=75";
|
||||
case "TootRain" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftootrain.34eff04b.png&w=640&q=75";
|
||||
case "Fedistar" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ffedistar.a2b814f8.png&w=256&q=75";
|
||||
case "Tooter" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftooter.1e9ff8f1.png&w=256&q=75";
|
||||
case "Amidon" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Famidon.3578681d.png&w=256&q=75";
|
||||
case "BREXXTODON" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fbrexxtodon.c6174a1a.png&w=256&q=75";
|
||||
case "DOStodon" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdostodon.cc63613e.png&w=256&q=75";
|
||||
case "Macstodon" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fmacstodon.a47d72c0.png&w=256&q=75";
|
||||
case "Masto9" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fmastonine.2f86e9a0.png&w=256&q=75";
|
||||
case "Mastodon for Apple II" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fmastodonforappleii.6b9ba8ef.png&w=256&q=75";
|
||||
case "Mastodon 3.11 for Workgroups" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fmastodonforworkgroups.c6524eb1.png&w=256&q=75";
|
||||
case "Heffalump" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fheffalump.b421f5e9.png&w=256&q=75";
|
||||
case "MOStodon" -> "https://joinmastodon.org/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fmostodon.bb1cb01c.png&w=3840&q=75";
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
package org.joinmastodon.android.ui.wrapstodon;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.ColorMatrixColorFilter;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.util.Property;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.AnnualReport;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.drawables.InnerShadowDrawable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class ArchetypeWrapScene extends AnnualWrapScene{
|
||||
private String username, domain, avaURL;
|
||||
private AnnualReport.Archetype archetype;
|
||||
|
||||
private Paint photoEffectPaint=new Paint();
|
||||
private Property<View, Float> photoDevelopEffectProp=new Property<>(Float.class, "fdsafdsa"){
|
||||
@Override
|
||||
public Float get(View object){
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(View object, Float value){
|
||||
int colorMod=-Math.round((1f-value)*255);
|
||||
photoEffectPaint.setColorFilter(new ColorMatrixColorFilter(new float[]{
|
||||
1, 0, 0, 0, colorMod,
|
||||
0, 1, 0, 0, colorMod,
|
||||
0, 0, 1, 0, colorMod,
|
||||
0, 0, 0, value, 0
|
||||
}));
|
||||
object.setLayerType(View.LAYER_TYPE_HARDWARE, photoEffectPaint);
|
||||
}
|
||||
};
|
||||
|
||||
public ArchetypeWrapScene(String username, String domain, String avaURL, AnnualReport.Archetype archetype){
|
||||
this.username=username;
|
||||
this.domain=domain;
|
||||
this.archetype=archetype;
|
||||
this.avaURL=avaURL;
|
||||
|
||||
photoEffectPaint.setAlpha(255);
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@Override
|
||||
protected View onCreateContentView(Context context){
|
||||
View view=context.getSystemService(LayoutInflater.class).inflate(R.layout.wrap_archetype, null);
|
||||
TextView subtitle=view.findViewById(R.id.subtitle);
|
||||
TextView username=view.findViewById(R.id.username);
|
||||
TextView domain=view.findViewById(R.id.domain);
|
||||
TextView archetypeTitle=view.findViewById(R.id.archetype_text);
|
||||
TextView archetypeExplanation=view.findViewById(R.id.archetype_explanation);
|
||||
View frame=view.findViewById(R.id.picture_frame);
|
||||
ImageView photo=view.findViewById(R.id.photo);
|
||||
RoundedFrameLayout photoWrap=view.findViewById(R.id.photo_wrap);
|
||||
|
||||
subtitle.setText(context.getString(R.string.yearly_wrap_archetype_subtitle, year));
|
||||
username.setText("@"+this.username);
|
||||
domain.setText("@"+this.domain);
|
||||
archetypeTitle.setText(switch(archetype){
|
||||
case LURKER -> R.string.yearly_wrap_archetype_lurker;
|
||||
case BOOSTER -> R.string.yearly_wrap_archetype_booster;
|
||||
case REPLIER -> R.string.yearly_wrap_archetype_replier;
|
||||
case POLLSTER -> R.string.yearly_wrap_archetype_pollster;
|
||||
case ORACLE -> R.string.yearly_wrap_archetype_oracle;
|
||||
});
|
||||
archetypeExplanation.setText("TBD");
|
||||
|
||||
frame.setOutlineProvider(OutlineProviders.roundedRect(4));
|
||||
frame.setClipToOutline(true);
|
||||
frame.setBackground(new RoundRectClippingDrawable(frame.getBackground()));
|
||||
photoWrap.setCornerRadius(V.dp(2));
|
||||
photoWrap.setForeground(new InnerShadowDrawable(V.dp(2), V.dp(1), 0x40000000, 0, V.dp(1)));
|
||||
photoWrap.setBackgroundColor(0xFF000000);
|
||||
if(avaURL!=null){
|
||||
ViewImageLoader.loadWithoutAnimation(photo, null, new UrlImageLoaderRequest(Bitmap.Config.ARGB_8888, 0, 0, List.of(), Uri.parse(avaURL)));
|
||||
}
|
||||
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
set.playTogether(
|
||||
ObjectAnimator.ofFloat(frame, View.ROTATION, -5, 2),
|
||||
ObjectAnimator.ofFloat(frame, View.SCALE_X, 1, 0.9f),
|
||||
ObjectAnimator.ofFloat(frame, View.SCALE_Y, 1, 0.9f),
|
||||
ObjectAnimator.ofFloat(photo, photoDevelopEffectProp, 0, 1)
|
||||
);
|
||||
set.setDuration(5000);
|
||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
set.start();
|
||||
set.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
photo.setLayerType(View.LAYER_TYPE_NONE, null);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroyContentView(){
|
||||
|
||||
}
|
||||
|
||||
private static class RoundRectClippingDrawable extends Drawable{
|
||||
private final Drawable inner;
|
||||
private Path path=new Path();
|
||||
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private RectF tmpRect=new RectF();
|
||||
|
||||
private RoundRectClippingDrawable(Drawable inner){
|
||||
this.inner=inner;
|
||||
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
|
||||
paint.setColor(0xff000000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas){
|
||||
if(canvas.isHardwareAccelerated()){
|
||||
inner.draw(canvas);
|
||||
return;
|
||||
}
|
||||
tmpRect.set(getBounds());
|
||||
canvas.saveLayer(tmpRect, null);
|
||||
inner.draw(canvas);
|
||||
canvas.drawPath(path, paint);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(@NonNull Rect bounds){
|
||||
super.onBoundsChange(bounds);
|
||||
inner.setBounds(bounds);
|
||||
path.rewind();
|
||||
path.addRoundRect(bounds.left, bounds.top, bounds.right, bounds.bottom, V.dp(4), V.dp(4), Path.Direction.CW);
|
||||
if(!path.isInverseFillType())
|
||||
path.toggleInverseFillType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable ColorFilter colorFilter){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity(){
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
package org.joinmastodon.android.ui.wrapstodon;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.AnnualReport;
|
||||
import org.joinmastodon.android.ui.utils.NestedScrollingTouchDisallower;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class ByTheNumbersWrapScene extends AnnualWrapScene{
|
||||
private static final int GRID_W=15, GRID_H=27;
|
||||
|
||||
private final AnnualReport.TypeDistribution typeDistribution;
|
||||
private int boostsCircleCount, repliesCircleCount, standaloneCircleCount;
|
||||
private int revealedCircleCount;
|
||||
private Drawable boostIcon, replyIcon, postIcon;
|
||||
private RectF tmpRect=new RectF();
|
||||
|
||||
public ByTheNumbersWrapScene(AnnualReport.TypeDistribution typeDistribution){
|
||||
this.typeDistribution=typeDistribution;
|
||||
int totalCircles=GRID_W*GRID_H;
|
||||
boostsCircleCount=Math.round(totalCircles*(typeDistribution.reblogs/(float)typeDistribution.total));
|
||||
repliesCircleCount=Math.round(totalCircles*(typeDistribution.replies/(float)typeDistribution.total));
|
||||
standaloneCircleCount=totalCircles-boostsCircleCount-repliesCircleCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View onCreateContentView(Context context){
|
||||
boostIcon=prepareIcon(context, R.drawable.ic_repeat_wght700grad200fill1_20px);
|
||||
replyIcon=prepareIcon(context, R.drawable.ic_reply_wght700_20px);
|
||||
postIcon=prepareIcon(context, R.drawable.ic_chat_bubble_wght700_20px);
|
||||
|
||||
ViewPager2 pager=new ViewPager2(context);
|
||||
pager.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
|
||||
pager.setAdapter(new PagerAdapter());
|
||||
pager.getChildAt(0).setOnTouchListener(new NestedScrollingTouchDisallower(pager));
|
||||
BackgroundDrawable bg=new BackgroundDrawable();
|
||||
pager.setBackground(bg);
|
||||
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels){
|
||||
if(position==0){
|
||||
revealedCircleCount=Math.round(boostsCircleCount*positionOffset);
|
||||
}else if(position==1){
|
||||
revealedCircleCount=boostsCircleCount+Math.round(repliesCircleCount*positionOffset);
|
||||
}else if(position==2){
|
||||
revealedCircleCount=boostsCircleCount+repliesCircleCount+Math.round(standaloneCircleCount*positionOffset);
|
||||
}else{
|
||||
revealedCircleCount=GRID_W*GRID_H;
|
||||
}
|
||||
pager.invalidate();
|
||||
}
|
||||
});
|
||||
return pager;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroyContentView(){
|
||||
|
||||
}
|
||||
|
||||
private Drawable prepareIcon(Context context, int res){
|
||||
Drawable d=context.getResources().getDrawable(res, context.getTheme()).mutate();
|
||||
d.setTint(0xFF17063B);
|
||||
d.setBounds(V.dp(2), V.dp(2), V.dp(14), V.dp(14));
|
||||
return d;
|
||||
}
|
||||
|
||||
private class PagerAdapter extends RecyclerView.Adapter<PageViewHolder>{
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public PageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new PageViewHolder(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull PageViewHolder holder, int position){
|
||||
Resources r=holder.itemView.getResources();
|
||||
holder.topText.setText(switch(position){
|
||||
case 0 -> r.getString(R.string.wrap_numbers_total_title);
|
||||
case 1 -> r.getString(R.string.wrap_numbers_boosts_title, typeDistribution.reblogs);
|
||||
case 2 -> r.getString(R.string.wrap_numbers_replies_title, typeDistribution.replies);
|
||||
case 3 -> r.getString(R.string.wrap_numbers_standalone_posts_title, typeDistribution.standalone);
|
||||
default -> throw new IllegalStateException("Unexpected value: "+position);
|
||||
});
|
||||
holder.number.setText(switch(position){
|
||||
case 0 -> NumberFormat.getInstance().format(typeDistribution.total);
|
||||
case 1 -> Math.round(typeDistribution.reblogs/(float)typeDistribution.total*100f)+"%";
|
||||
case 2 -> Math.round(typeDistribution.replies/(float)typeDistribution.total*100f)+"%";
|
||||
case 3 -> Math.round(typeDistribution.standalone/(float)typeDistribution.total*100f)+"%";
|
||||
default -> throw new IllegalStateException("Unexpected value: "+position);
|
||||
});
|
||||
holder.bottomText.setText(switch(position){
|
||||
case 0 -> r.getString(R.string.wrap_numbers_x_times_in_year, year);
|
||||
case 1 -> r.getString(R.string.wrap_numbers_boosts);
|
||||
case 2 -> r.getString(R.string.wrap_numbers_replies);
|
||||
case 3 -> r.getString(R.string.wrap_numbers_standalone_posts);
|
||||
default -> throw new IllegalStateException("Unexpected value: "+position);
|
||||
});
|
||||
holder.itemView.setBackgroundTintList(ColorStateList.valueOf(switch(position){
|
||||
case 0, 1 -> 0xFFFFBE2E;
|
||||
case 2 -> 0xFF858AFA;
|
||||
case 3 -> 0xFFBAFF3B;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+position);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
private static class PageViewHolder extends RecyclerView.ViewHolder{
|
||||
private final TextView topText, number, bottomText;
|
||||
|
||||
public PageViewHolder(@NonNull ViewGroup parent){
|
||||
super(LayoutInflater.from(parent.getContext()).inflate(R.layout.wrap_numbers_page, parent, false));
|
||||
topText=itemView.findViewById(R.id.top_text);
|
||||
number=itemView.findViewById(R.id.number);
|
||||
bottomText=itemView.findViewById(R.id.bottom_text);
|
||||
}
|
||||
}
|
||||
|
||||
private class BackgroundDrawable extends Drawable{
|
||||
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas){
|
||||
Rect bounds=getBounds();
|
||||
float spacing=(bounds.width()-V.dp(16+16*GRID_W))/(float)(GRID_W-1);
|
||||
int i=0;
|
||||
float radius=V.dp(8);
|
||||
canvas.save();
|
||||
canvas.translate(radius, radius);
|
||||
for(int y=0;y<GRID_H;y++){
|
||||
canvas.save();
|
||||
for(int x=0;x<GRID_W;x++){
|
||||
int color;
|
||||
Drawable icon;
|
||||
if(i<revealedCircleCount){
|
||||
if(i<boostsCircleCount){
|
||||
color=0xFFFFBE2E;
|
||||
icon=boostIcon;
|
||||
}else if(i<boostsCircleCount+repliesCircleCount){
|
||||
color=0xFF858AFA;
|
||||
icon=replyIcon;
|
||||
}else{
|
||||
color=0xFFBAFF3B;
|
||||
icon=postIcon;
|
||||
}
|
||||
}else{
|
||||
color=0xFF2F0C7A;
|
||||
icon=null;
|
||||
}
|
||||
paint.setColor(color);
|
||||
canvas.drawCircle(radius, radius, radius, paint);
|
||||
if(icon!=null){
|
||||
icon.draw(canvas);
|
||||
}
|
||||
i++;
|
||||
canvas.translate(radius*2+spacing, 0);
|
||||
}
|
||||
canvas.restore();
|
||||
canvas.translate(0, radius*2+spacing);
|
||||
}
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable ColorFilter colorFilter){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity(){
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
package org.joinmastodon.android.ui.wrapstodon;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.RelativeSizeSpan;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.text.style.SuperscriptSpan;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.AnnualReport;
|
||||
import org.joinmastodon.android.ui.drawables.EmptyDrawable;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.NestableScrollView;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class FavoriteAccountsWrapScene extends AnnualWrapScene{
|
||||
private final Map<String, Account> accounts;
|
||||
private final List<AnnualReport.AccountAndCount> topAccounts;
|
||||
private LinearLayout scrollContent;
|
||||
|
||||
public FavoriteAccountsWrapScene(Map<String, Account> accounts, List<AnnualReport.AccountAndCount> topAccounts){
|
||||
this.accounts=accounts;
|
||||
this.topAccounts=topAccounts;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View onCreateContentView(Context context){
|
||||
NestableScrollView scroll=new NestableScrollView(context);
|
||||
scroll.setNestedScrollingEnabled(true);
|
||||
LayoutInflater inflater=LayoutInflater.from(context);
|
||||
|
||||
LinearLayout ll=new LinearLayout(context);
|
||||
ll.setOrientation(LinearLayout.VERTICAL);
|
||||
scrollContent=ll;
|
||||
scroll.addView(ll);
|
||||
|
||||
View header=inflater.inflate(R.layout.wrap_faves_header, ll, false);
|
||||
TextView title=header.findViewById(R.id.title);
|
||||
TextView subtitle=header.findViewById(R.id.subtitle);
|
||||
title.setText(replaceBoldWithColor(context.getResources().getText(R.string.wrap_most_reblogged_title), 0xFFBAFF3B));
|
||||
SpannableStringBuilder subtitleStr=new SpannableStringBuilder(context.getResources().getText(R.string.wrap_most_reblogged_subtitle));
|
||||
int index=subtitleStr.toString().indexOf("%s");
|
||||
if(index!=-1){
|
||||
subtitleStr.replace(index, index+2, year);
|
||||
}
|
||||
subtitle.setText(replaceBoldWithColor(subtitleStr, 0xFFFFBE2E));
|
||||
ll.addView(header);
|
||||
|
||||
for(int i=0;i<Math.min(topAccounts.size(), 4);i++){
|
||||
int boostCount=topAccounts.get(i).count;
|
||||
Account account=accounts.get(topAccounts.get(i).accountId);
|
||||
if(account==null)
|
||||
continue;
|
||||
LinearLayout row=(LinearLayout) inflater.inflate(R.layout.wrap_faves_account_row, ll, false);
|
||||
row.setDividerDrawable(new EmptyDrawable(V.dp(16), 1));
|
||||
row.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE);
|
||||
if(i%2==1){
|
||||
View firstChild=row.getChildAt(0);
|
||||
row.removeView(firstChild);
|
||||
row.addView(firstChild);
|
||||
}
|
||||
ll.addView(row);
|
||||
|
||||
TextView rank=row.findViewById(R.id.rank);
|
||||
TextView count=row.findViewById(R.id.count);
|
||||
ImageView cover=row.findViewById(R.id.cover);
|
||||
ImageView ava=row.findViewById(R.id.avatar);
|
||||
TextView name=row.findViewById(R.id.name);
|
||||
TextView username=row.findViewById(R.id.username);
|
||||
TextView postsCount=row.findViewById(R.id.posts_value);
|
||||
TextView postsLabel=row.findViewById(R.id.posts_label);
|
||||
TextView followersCount=row.findViewById(R.id.followers_value);
|
||||
TextView followersLabel=row.findViewById(R.id.followers_label);
|
||||
TextView followingCount=row.findViewById(R.id.following_value);
|
||||
TextView followingLabel=row.findViewById(R.id.following_label);
|
||||
|
||||
SpannableString rankStr=new SpannableString("#"+(i+1));
|
||||
rankStr.setSpan(new SuperscriptSpan(), 0, 1, 0);
|
||||
rankStr.setSpan(new RelativeSizeSpan(0.6f), 0, 1, 0);
|
||||
rank.setText(rankStr);
|
||||
count.setText(context.getResources().getQuantityString(R.plurals.x_reblogs, boostCount, boostCount));
|
||||
|
||||
CharSequence nameStr=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
|
||||
name.setText(nameStr);
|
||||
UiUtils.loadCustomEmojiInTextView(name);
|
||||
username.setText(account.getDisplayUsername());
|
||||
postsCount.setText(UiUtils.abbreviateNumber(account.statusesCount));
|
||||
postsLabel.setText(context.getResources().getQuantityString(R.plurals.posts, account.statusesCount>1000 ? 1000 : (int)account.statusesCount));
|
||||
followersCount.setText(UiUtils.abbreviateNumber(account.followersCount));
|
||||
followersLabel.setText(context.getResources().getQuantityString(R.plurals.followers, account.followersCount>1000 ? 1000 : (int)account.followersCount));
|
||||
followingCount.setText(UiUtils.abbreviateNumber(account.followingCount));
|
||||
followingLabel.setText(context.getResources().getQuantityString(R.plurals.following, account.followingCount>1000 ? 1000 : (int)account.followingCount));
|
||||
|
||||
ViewImageLoader.loadWithoutAnimation(cover, null, new UrlImageLoaderRequest(Bitmap.Config.ARGB_8888, V.dp(300), 0, List.of(), Uri.parse(account.headerStatic)));
|
||||
ViewImageLoader.loadWithoutAnimation(ava, null, new UrlImageLoaderRequest(Bitmap.Config.ARGB_8888, V.dp(56), V.dp(56), List.of(), Uri.parse(account.avatarStatic)));
|
||||
}
|
||||
|
||||
return scroll;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroyContentView(){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getViewForScreenshot(){
|
||||
return scrollContent;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,236 @@
|
|||
package org.joinmastodon.android.ui.wrapstodon;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.AnnualReport;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.FixedAspectRatioImageView;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class FavoriteHashtagsWrapScene extends AnnualWrapScene{
|
||||
private final List<AnnualReport.NameAndCount> tags;
|
||||
private int[] buffer;
|
||||
private Paint paint=new Paint();
|
||||
private ByteBuffer pixelBuffer;
|
||||
|
||||
public FavoriteHashtagsWrapScene(List<AnnualReport.NameAndCount> tags){
|
||||
this.tags=tags.stream().sorted(Comparator.comparingInt((AnnualReport.NameAndCount t)->t.count).reversed()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View onCreateContentView(Context context){
|
||||
LinearLayout ll=new LinearLayout(context);
|
||||
ll.setOrientation(LinearLayout.VERTICAL);
|
||||
LayoutInflater inflater=LayoutInflater.from(context);
|
||||
|
||||
View header=inflater.inflate(R.layout.wrap_faves_header, ll, false);
|
||||
TextView title=header.findViewById(R.id.title);
|
||||
TextView subtitle=header.findViewById(R.id.subtitle);
|
||||
title.setText(replaceBoldWithColor(context.getResources().getText(R.string.wrap_favorite_tags_title), 0xFFBAFF3B));
|
||||
subtitle.setText(R.string.wrap_favorite_tags_subtitle);
|
||||
ll.addView(header);
|
||||
|
||||
FrameLayout tagsCanvas=new FrameLayout(context);
|
||||
ll.addView(tagsCanvas, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(360)));
|
||||
|
||||
Typeface font;
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
|
||||
font=context.getResources().getFont(R.font.manrope_w400);
|
||||
}else{
|
||||
font=Typeface.DEFAULT;
|
||||
}
|
||||
int max=tags.get(0).count;
|
||||
int min=tags.get(tags.size()-1).count;
|
||||
buffer=new int[12*360];
|
||||
paint.setTypeface(font);
|
||||
paint.setColor(0xFF000000);
|
||||
paint.setShadowLayer(1, 0, 0, 0xFF000000);
|
||||
pixelBuffer=ByteBuffer.allocate(120*384);
|
||||
int[] spriteSize={0, 0};
|
||||
int[] xy={0, 0};
|
||||
Random rand=new Random();
|
||||
// int i=0;
|
||||
for(AnnualReport.NameAndCount tag:tags){
|
||||
// if(i==20) break;
|
||||
int size;
|
||||
if(max==min){
|
||||
size=80;
|
||||
}else{
|
||||
float fraction=(tag.count-min)/(float)(max-min);
|
||||
size=UiUtils.lerp(15, 80, fraction*fraction*fraction/*fraction*/);
|
||||
paint.setShadowLayer(fraction, 0, 0, 0xFF000000);
|
||||
}
|
||||
paint.setTextSize(size);
|
||||
while(paint.measureText(tag.name)>360){
|
||||
size/=2;
|
||||
paint.setTextSize(size);
|
||||
}
|
||||
|
||||
int[] sprite=getWordSprite(tag.name, size, spriteSize);
|
||||
// int startX=360/2-spriteSize[0]/2;
|
||||
// int startY=360/2-spriteSize[1]/2;
|
||||
int startX=rand.nextInt(360-spriteSize[0]);
|
||||
int startY=rand.nextInt(360-spriteSize[1]);
|
||||
double dt=rand.nextBoolean() ? 1 : -1;
|
||||
double t=-dt;
|
||||
int maxDelta=509;
|
||||
int dx, dy;
|
||||
boolean placed=false;
|
||||
int x, y;
|
||||
do{
|
||||
spiral(t+=dt, xy);
|
||||
dx=xy[0];
|
||||
dy=xy[1];
|
||||
x=startX+dx;
|
||||
y=startY+dy;
|
||||
if(x<0 || y<0 || x+spriteSize[0]>360 || y+spriteSize[1]>360)
|
||||
continue;
|
||||
if(!collide(sprite, x, y, spriteSize[0], spriteSize[1])){
|
||||
addMask(sprite, x, y, spriteSize[0], spriteSize[1]);
|
||||
placed=true;
|
||||
break;
|
||||
}
|
||||
}while(Math.max(Math.abs(dx), Math.abs(dy))<maxDelta);
|
||||
// i++;
|
||||
if(!placed)
|
||||
break;
|
||||
|
||||
TextView text=new TextView(context);
|
||||
text.setTypeface(font);
|
||||
text.setTextSize(TypedValue.COMPLEX_UNIT_DIP, size);
|
||||
text.setTextColor(0xFFCCCFFF);
|
||||
text.setSingleLine();
|
||||
text.setText(tag.name);
|
||||
text.setTranslationX(V.dp(x));
|
||||
text.setTranslationY(V.dp(y));
|
||||
tagsCanvas.addView(text, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.TOP|Gravity.LEFT));
|
||||
}
|
||||
|
||||
if(BuildConfig.DEBUG){
|
||||
tagsCanvas.setOnClickListener(v->{
|
||||
FixedAspectRatioImageView vv=new FixedAspectRatioImageView(context);
|
||||
vv.setAspectRatio(1);
|
||||
vv.setBackground(new BitmapDrawable(getDebugBitmap()));
|
||||
new AlertDialog.Builder(context)
|
||||
.setView(vv)
|
||||
.show();
|
||||
});
|
||||
}
|
||||
|
||||
return ll;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroyContentView(){
|
||||
|
||||
}
|
||||
|
||||
private int[] getWordSprite(String text, int size, int[] outSize){
|
||||
// Makes a binary image of the string
|
||||
paint.setTextSize(size);
|
||||
int w=Math.min(384, (int)paint.measureText(text)+2);
|
||||
if(w%32!=0){
|
||||
w+=32-w%32;
|
||||
}
|
||||
int h=(int)(paint.descent()-paint.ascent());
|
||||
Bitmap bmp=Bitmap.createBitmap(w, h, Bitmap.Config.ALPHA_8);
|
||||
new Canvas(bmp).drawText(text, 1, -paint.ascent(), paint);
|
||||
bmp.copyPixelsToBuffer(pixelBuffer);
|
||||
pixelBuffer.rewind();
|
||||
outSize[0]=w;
|
||||
outSize[1]=h;
|
||||
int spriteW=w >> 5;
|
||||
int[] sprite=new int[spriteW*h];
|
||||
byte[] pixelData=pixelBuffer.array();
|
||||
int offsetIntoPixelData=0;
|
||||
int stride=bmp.getRowBytes();
|
||||
for(int y=0;y<h;y++){
|
||||
for(int x=0;x<spriteW;x++){
|
||||
int slice=0;
|
||||
for(int i=0;i<32;i++){
|
||||
slice<<=1;
|
||||
if(pixelData[offsetIntoPixelData+(x << 5)+i]!=0)
|
||||
slice|=1;
|
||||
}
|
||||
sprite[y*spriteW+x]=slice;
|
||||
}
|
||||
offsetIntoPixelData+=stride;
|
||||
}
|
||||
return sprite;
|
||||
}
|
||||
|
||||
private void spiral(double t, int[] outXY){
|
||||
t*=3;
|
||||
outXY[0]=(int)Math.round(t*Math.cos(t));
|
||||
outXY[1]=(int)Math.round(t*Math.sin(t));
|
||||
}
|
||||
|
||||
private boolean collide(int[] sprite, int x, int y, int w, int h){
|
||||
int packedWidth=w>>5, packedX=x>>5;
|
||||
int shiftX=x & 0x7f, invShiftX=32-shiftX;
|
||||
int last;
|
||||
for(int iy=0;iy<h;iy++){
|
||||
last=0;
|
||||
for(int ix=0;ix<=packedWidth;ix++){
|
||||
if((((last << invShiftX) | (ix<packedWidth ? (last=sprite[iy*packedWidth+ix]) >>> shiftX : 0)) & buffer[(iy+y)*12+ix+packedX])!=0)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void addMask(int[] sprite, int x, int y, int w, int h){
|
||||
int packedWidth=w>>5, packedX=x>>5;
|
||||
int shiftX=x & 0x7f, invShiftX=32-shiftX;
|
||||
int last;
|
||||
for(int iy=0;iy<h;iy++){
|
||||
last=0;
|
||||
for(int ix=0;ix<=packedWidth;ix++){
|
||||
buffer[(iy+y)*12+ix+packedX]|=(last << invShiftX) | (ix<packedWidth ? (last=sprite[iy*packedWidth+ix]) >>> shiftX : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Bitmap getDebugBitmap(){
|
||||
Bitmap bmp=Bitmap.createBitmap(384, 360, Bitmap.Config.ALPHA_8);
|
||||
for(int y=0;y<360;y++){
|
||||
for(int x=0;x<12;x++){
|
||||
int packed=buffer[y*12+x];
|
||||
for(int i=0;i<32;i++){
|
||||
if((packed & 0x80000000)!=0){
|
||||
bmp.setPixel(x*32+i, y, 0xFF000000);
|
||||
}
|
||||
packed<<=1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return bmp;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,324 @@
|
|||
package org.joinmastodon.android.ui.wrapstodon;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.DashPathEffect;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.RadialGradient;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.net.Uri;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.AnnualReport;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class InteractedAccountsWrapScene extends AnnualWrapScene{
|
||||
private final Account self;
|
||||
private final Map<String, Account> allAccounts;
|
||||
private final List<AnnualReport.AccountAndCount> accounts;
|
||||
private List<ImageView> avatarViews=new ArrayList<>();
|
||||
private ImageView avatarToFollowAround;
|
||||
private final Runnable followAroundAnimationUpdater=this::updateFollowAroundAnimation;
|
||||
private View content;
|
||||
private FrameLayout contentWrap;
|
||||
private FrameLayout orbit;
|
||||
private ImageView selfAva;
|
||||
private View orbitBGView;
|
||||
private TextView title, subtitle;
|
||||
private View detailsOverlay;
|
||||
private TextView detailsNumber, detailsText;
|
||||
private Animator currentZoomAnim;
|
||||
|
||||
public InteractedAccountsWrapScene(Account self, Map<String, Account> allAccounts, List<AnnualReport.AccountAndCount> accounts){
|
||||
this.self=self;
|
||||
this.allAccounts=allAccounts;
|
||||
this.accounts=accounts;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View onCreateContentView(Context context){
|
||||
LayoutInflater inflater=LayoutInflater.from(context);
|
||||
contentWrap=(FrameLayout) inflater.inflate(R.layout.wrap_interacted_accounts, null);
|
||||
content=contentWrap.findViewById(R.id.content);
|
||||
|
||||
selfAva=content.findViewById(R.id.self_ava);
|
||||
ViewImageLoader.loadWithoutAnimation(selfAva, null, new UrlImageLoaderRequest(Bitmap.Config.ARGB_8888, V.dp(160), V.dp(160), List.of(), Uri.parse(self.avatarStatic)));
|
||||
title=content.findViewById(R.id.title);
|
||||
subtitle=content.findViewById(R.id.subtitle);
|
||||
detailsOverlay=contentWrap.findViewById(R.id.details_overlay);
|
||||
detailsNumber=contentWrap.findViewById(R.id.details_number);
|
||||
detailsText=contentWrap.findViewById(R.id.details_text);
|
||||
|
||||
orbit=content.findViewById(R.id.orbit);
|
||||
orbitBGView=content.findViewById(R.id.orbit_bg);
|
||||
LayerDrawable orbitBG=new LayerDrawable(new Drawable[]{
|
||||
new OrbitBackgroundDrawable(),
|
||||
new OrbitBackgroundDrawable()
|
||||
});
|
||||
orbitBG.setLayerSize(0, V.dp(409+4), V.dp(409+4));
|
||||
orbitBG.setLayerSize(1, V.dp(256+4), V.dp(256+4));
|
||||
orbitBG.setLayerGravity(0, Gravity.CENTER);
|
||||
orbitBG.setLayerGravity(1, Gravity.CENTER);
|
||||
orbitBGView.setBackground(orbitBG);
|
||||
FrameLayout innerRing=content.findViewById(R.id.inner_ring);
|
||||
FrameLayout outerRing=content.findViewById(R.id.outer_ring);
|
||||
|
||||
List<AnnualReport.AccountAndCount> forInnerRing=accounts.subList(0, Math.min(6, accounts.size()));
|
||||
List<AnnualReport.AccountAndCount> forOuterRing=accounts.size()>6 ? accounts.subList(6, Math.min(18, accounts.size())) : List.of();
|
||||
if(forInnerRing.isEmpty())
|
||||
return content;
|
||||
|
||||
final long innerDuration=15000, outerDuration=20000;
|
||||
ArrayList<Animator> anims=new ArrayList<>();
|
||||
anims.add(repeat(ObjectAnimator.ofFloat(innerRing, View.ROTATION, 0, 360).setDuration(innerDuration)));
|
||||
anims.add(repeat(ObjectAnimator.ofFloat(outerRing, View.ROTATION, 0, 360).setDuration(outerDuration)));
|
||||
|
||||
double angle=0;
|
||||
double deltaAngle=2*Math.PI/forInnerRing.size();
|
||||
for(AnnualReport.AccountAndCount acc:forInnerRing){
|
||||
Account account=allAccounts.get(acc.accountId);
|
||||
if(account==null)
|
||||
continue;
|
||||
ImageView iv=makeImageView(context, account);
|
||||
innerRing.addView(iv, new FrameLayout.LayoutParams(V.dp(40), V.dp(40), Gravity.CENTER));
|
||||
float r=V.dp(128);
|
||||
iv.setTranslationX(r*(float)Math.cos(angle));
|
||||
iv.setTranslationY(r*(float)Math.sin(angle));
|
||||
iv.setOnClickListener(this::onAvatarClick);
|
||||
iv.setTag(acc);
|
||||
anims.add(repeat(ObjectAnimator.ofFloat(iv, View.ROTATION, 0, -360).setDuration(innerDuration)));
|
||||
avatarViews.add(iv);
|
||||
angle+=deltaAngle;
|
||||
}
|
||||
|
||||
if(!forOuterRing.isEmpty()){
|
||||
angle=0;
|
||||
deltaAngle=2*Math.PI/forOuterRing.size();
|
||||
for(AnnualReport.AccountAndCount acc:forOuterRing){
|
||||
Account account=allAccounts.get(acc.accountId);
|
||||
if(account==null)
|
||||
continue;
|
||||
ImageView iv=makeImageView(context, account);
|
||||
outerRing.addView(iv, new FrameLayout.LayoutParams(V.dp(40), V.dp(40), Gravity.CENTER));
|
||||
float r=V.dp(204.5f);
|
||||
iv.setTranslationX(r*(float)Math.cos(angle));
|
||||
iv.setTranslationY(r*(float)Math.sin(angle));
|
||||
iv.setOnClickListener(this::onAvatarClick);
|
||||
iv.setTag(acc);
|
||||
anims.add(repeat(ObjectAnimator.ofFloat(iv, View.ROTATION, 0, -360).setDuration(outerDuration)));
|
||||
avatarViews.add(iv);
|
||||
angle+=deltaAngle;
|
||||
}
|
||||
}
|
||||
|
||||
detailsOverlay.setVisibility(View.GONE);
|
||||
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
set.playTogether(anims);
|
||||
set.setInterpolator(new LinearInterpolator());
|
||||
set.start();
|
||||
|
||||
return contentWrap;
|
||||
}
|
||||
|
||||
// Because Android's animation API is stupid
|
||||
private ObjectAnimator repeat(ObjectAnimator anim){
|
||||
anim.setRepeatCount(ObjectAnimator.INFINITE);
|
||||
return anim;
|
||||
}
|
||||
|
||||
private ImageView makeImageView(Context context, Account account){
|
||||
RoundedImageView iv=new RoundedImageView(context);
|
||||
iv.setCornerRadius(V.dp(20));
|
||||
iv.setForeground(iv.getResources().getDrawable(R.drawable.wrap_ava_border_1dp, iv.getContext().getTheme()));
|
||||
ViewImageLoader.loadWithoutAnimation(iv, null, new UrlImageLoaderRequest(Bitmap.Config.ARGB_8888, V.dp(40), V.dp(40), List.of(), Uri.parse(account.avatarStatic)));
|
||||
return iv;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroyContentView(){
|
||||
|
||||
}
|
||||
|
||||
private void onAvatarClick(View v){
|
||||
if(currentZoomAnim!=null)
|
||||
currentZoomAnim.cancel();
|
||||
ImageView iv=(ImageView) v;
|
||||
AnnualReport.AccountAndCount acc=(AnnualReport.AccountAndCount) v.getTag();
|
||||
((ViewGroup)content).setClipChildren(false);
|
||||
orbit.setClipChildren(false);
|
||||
contentWrap.setClipChildren(false);
|
||||
avatarToFollowAround=iv;
|
||||
content.setPivotX(0);
|
||||
content.setPivotY(0);
|
||||
detailsNumber.setText(NumberFormat.getInstance().format(acc.count));
|
||||
detailsOverlay.setVisibility(View.VISIBLE);
|
||||
detailsOverlay.setAlpha(0);
|
||||
SpannableStringBuilder ssb=new SpannableStringBuilder(v.getResources().getQuantityString(R.plurals.wrap_replies_exchanged, acc.count));
|
||||
int index=ssb.toString().indexOf("%s");
|
||||
if(index!=-1){
|
||||
String name=Objects.requireNonNull(allAccounts.get(acc.accountId)).displayName;
|
||||
ssb.replace(index, index+2, name);
|
||||
ssb.setSpan(new ForegroundColorSpan(0xFFBAFF3B), index, index+name.length(), 0);
|
||||
}
|
||||
detailsText.setText(ssb);
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
ArrayList<Animator> anims=new ArrayList<>();
|
||||
anims.add(ObjectAnimator.ofFloat(content, View.SCALE_X, 3.2f));
|
||||
anims.add(ObjectAnimator.ofFloat(content, View.SCALE_Y, 3.2f));
|
||||
anims.add(ObjectAnimator.ofFloat(selfAva, View.ALPHA, .15f));
|
||||
anims.add(ObjectAnimator.ofFloat(orbitBGView, View.ALPHA, .15f));
|
||||
anims.add(ObjectAnimator.ofFloat(title, View.ALPHA, .15f));
|
||||
anims.add(ObjectAnimator.ofFloat(subtitle, View.ALPHA, .15f));
|
||||
for(ImageView ava:avatarViews){
|
||||
if(ava!=iv){
|
||||
anims.add(ObjectAnimator.ofFloat(ava, View.ALPHA, .15f));
|
||||
}
|
||||
ava.setEnabled(false);
|
||||
}
|
||||
for(Animator a:anims)
|
||||
a.setDuration(1000);
|
||||
ObjectAnimator detailFadeIn=ObjectAnimator.ofFloat(detailsOverlay, View.ALPHA, 1);
|
||||
detailFadeIn.setDuration(200);
|
||||
detailFadeIn.setStartDelay(800);
|
||||
anims.add(detailFadeIn);
|
||||
set.playTogether(anims);
|
||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
set.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
currentZoomAnim=null;
|
||||
}
|
||||
});
|
||||
currentZoomAnim=set;
|
||||
set.start();
|
||||
contentView.postOnAnimation(followAroundAnimationUpdater);
|
||||
contentWrap.setOnClickListener(v_->zoomOut());
|
||||
}
|
||||
|
||||
private void updateFollowAroundAnimation(){
|
||||
if(avatarToFollowAround==null)
|
||||
return;
|
||||
int[] loc={0, 0};
|
||||
content.setTranslationX(0);
|
||||
content.setTranslationY(0);
|
||||
avatarToFollowAround.getLocationInWindow(loc);
|
||||
float avaX=loc[0], avaY=loc[1];
|
||||
content.getLocationInWindow(loc);
|
||||
float contX=loc[0], contY=loc[1];
|
||||
|
||||
float xOffset=content.getLayoutDirection()==View.LAYOUT_DIRECTION_RTL ? content.getWidth()-V.dp(21+128) : V.dp(21);
|
||||
content.setTranslationX((contX-avaX+xOffset)*(content.getScaleX()-1f)/2.2f);
|
||||
content.setTranslationY((contY-avaY+content.getHeight()/2f-V.dp(64))*(content.getScaleY()-1f)/2.2f);
|
||||
contentView.postOnAnimation(followAroundAnimationUpdater);
|
||||
}
|
||||
|
||||
private void zoomOut(){
|
||||
if(currentZoomAnim!=null)
|
||||
currentZoomAnim.cancel();
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
ArrayList<Animator> anims=new ArrayList<>();
|
||||
anims.add(ObjectAnimator.ofFloat(content, View.SCALE_X, 1));
|
||||
anims.add(ObjectAnimator.ofFloat(content, View.SCALE_Y, 1));
|
||||
anims.add(ObjectAnimator.ofFloat(selfAva, View.ALPHA, 1));
|
||||
anims.add(ObjectAnimator.ofFloat(orbitBGView, View.ALPHA, 1));
|
||||
anims.add(ObjectAnimator.ofFloat(title, View.ALPHA, 1));
|
||||
anims.add(ObjectAnimator.ofFloat(subtitle, View.ALPHA, 1));
|
||||
for(ImageView ava:avatarViews){
|
||||
anims.add(ObjectAnimator.ofFloat(ava, View.ALPHA, 1));
|
||||
ava.setEnabled(true);
|
||||
}
|
||||
for(Animator a:anims)
|
||||
a.setDuration(500);
|
||||
anims.add(ObjectAnimator.ofFloat(detailsOverlay, View.ALPHA, 0).setDuration(150));
|
||||
set.playTogether(anims);
|
||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
set.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
avatarToFollowAround=null;
|
||||
contentWrap.setOnClickListener(null);
|
||||
detailsOverlay.setVisibility(View.GONE);
|
||||
currentZoomAnim=null;
|
||||
}
|
||||
});
|
||||
currentZoomAnim=set;
|
||||
set.start();
|
||||
}
|
||||
|
||||
private class OrbitBackgroundDrawable extends Drawable{
|
||||
private Paint strokePaint=new Paint(Paint.ANTI_ALIAS_FLAG), fillPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private RadialGradient gradient;
|
||||
private Matrix matrix=new Matrix();
|
||||
|
||||
public OrbitBackgroundDrawable(){
|
||||
strokePaint.setStyle(Paint.Style.STROKE);
|
||||
strokePaint.setStrokeWidth(V.dp(4));
|
||||
strokePaint.setPathEffect(new DashPathEffect(new float[]{V.dp(4), V.dp(12)}, 0));
|
||||
strokePaint.setColor(0xFF562CFC);
|
||||
strokePaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
|
||||
fillPaint.setShader(gradient=new RadialGradient(0f, 0f, 1f, new int[]{0x8017063B, 0x8017063B, 0x802F0C7A}, new float[]{0f, .77f, 1f}, Shader.TileMode.CLAMP));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas){
|
||||
Rect bounds=getBounds();
|
||||
float radius=bounds.width()/2f-V.dp(2);
|
||||
matrix.setTranslate(bounds.centerX(), bounds.centerY());
|
||||
matrix.postScale(radius, radius, bounds.centerX(), bounds.centerY());
|
||||
gradient.setLocalMatrix(matrix);
|
||||
canvas.drawCircle(bounds.centerX(), bounds.centerY(), radius, fillPaint);
|
||||
canvas.drawCircle(bounds.centerX(), bounds.centerY(), radius, strokePaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable ColorFilter colorFilter){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity(){
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package org.joinmastodon.android.ui.wrapstodon;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
|
||||
public class RoundedFrameLayout extends FrameLayout{
|
||||
private int cornerRadius;
|
||||
private boolean roundBottomCorners=true;
|
||||
private Paint clearPaint=new Paint(Paint.ANTI_ALIAS_FLAG), paint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
public RoundedFrameLayout(Context context){
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public RoundedFrameLayout(Context context, AttributeSet attrs){
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public RoundedFrameLayout(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.RoundedImageView);
|
||||
cornerRadius=ta.getDimensionPixelOffset(R.styleable.RoundedImageView_cornerRadius, 0);
|
||||
roundBottomCorners=ta.getBoolean(R.styleable.RoundedImageView_roundBottomCorners, true);
|
||||
ta.recycle();
|
||||
setOutlineProvider(new ViewOutlineProvider(){
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline){
|
||||
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight()+(roundBottomCorners ? 0 : cornerRadius), cornerRadius);
|
||||
}
|
||||
});
|
||||
setClipToOutline(true);
|
||||
clearPaint.setColor(0xFFFFFFFF);
|
||||
clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
|
||||
paint.setColor(0xFF0ff000);
|
||||
}
|
||||
|
||||
public void setCornerRadius(int cornerRadius){
|
||||
this.cornerRadius=cornerRadius;
|
||||
invalidateOutline();
|
||||
}
|
||||
|
||||
public void setRoundBottomCorners(boolean roundBottomCorners){
|
||||
this.roundBottomCorners=roundBottomCorners;
|
||||
invalidateOutline();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas){
|
||||
if(canvas.isHardwareAccelerated()){
|
||||
super.draw(canvas);
|
||||
return;
|
||||
}
|
||||
canvas.saveLayer(0, 0, getWidth(), getHeight(), null);
|
||||
canvas.drawRoundRect(0, 0, getWidth(), getHeight()+(roundBottomCorners ? 0 : cornerRadius), cornerRadius, cornerRadius, paint);
|
||||
canvas.saveLayer(0, 0, getWidth(), getHeight(), clearPaint);
|
||||
super.draw(canvas);
|
||||
canvas.restore();
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package org.joinmastodon.android.ui.wrapstodon;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
|
||||
/**
|
||||
* Software-rendering-friendly rounded-corners image view. Relies on arcane xrefmode magic.
|
||||
*/
|
||||
public class RoundedImageView extends ImageView{
|
||||
private int cornerRadius;
|
||||
private boolean roundBottomCorners=true;
|
||||
private Paint clearPaint=new Paint(Paint.ANTI_ALIAS_FLAG), paint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
public RoundedImageView(Context context){
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public RoundedImageView(Context context, AttributeSet attrs){
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public RoundedImageView(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.RoundedImageView);
|
||||
cornerRadius=ta.getDimensionPixelOffset(R.styleable.RoundedImageView_cornerRadius, 0);
|
||||
roundBottomCorners=ta.getBoolean(R.styleable.RoundedImageView_roundBottomCorners, true);
|
||||
ta.recycle();
|
||||
setOutlineProvider(new ViewOutlineProvider(){
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline){
|
||||
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight()+(roundBottomCorners ? 0 : cornerRadius), cornerRadius);
|
||||
}
|
||||
});
|
||||
setClipToOutline(true);
|
||||
clearPaint.setColor(0xFFFFFFFF);
|
||||
clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
|
||||
paint.setColor(0xFF0ff000);
|
||||
}
|
||||
|
||||
public void setCornerRadius(int cornerRadius){
|
||||
this.cornerRadius=cornerRadius;
|
||||
invalidateOutline();
|
||||
}
|
||||
|
||||
public void setRoundBottomCorners(boolean roundBottomCorners){
|
||||
this.roundBottomCorners=roundBottomCorners;
|
||||
invalidateOutline();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas){
|
||||
if(canvas.isHardwareAccelerated()){
|
||||
super.draw(canvas);
|
||||
return;
|
||||
}
|
||||
canvas.saveLayer(0, 0, getWidth(), getHeight(), null);
|
||||
canvas.drawRoundRect(0, 0, getWidth(), getHeight()+(roundBottomCorners ? 0 : cornerRadius), cornerRadius, cornerRadius, paint);
|
||||
canvas.saveLayer(0, 0, getWidth(), getHeight(), clearPaint);
|
||||
super.draw(canvas);
|
||||
canvas.restore();
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
package org.joinmastodon.android.ui.wrapstodon;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.AnnualReport;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class SummaryWrapScene extends AnnualWrapScene{
|
||||
private final Map<String, Account> allAccounts;
|
||||
private final Map<String, Status> allStatuses;
|
||||
private final AnnualReport report;
|
||||
private final Account self;
|
||||
private Drawable postIcon;
|
||||
|
||||
public SummaryWrapScene(Account self, Map<String, Account> allAccounts, Map<String, Status> allStatuses, AnnualReport report){
|
||||
this.allAccounts=allAccounts;
|
||||
this.allStatuses=allStatuses;
|
||||
this.report=report;
|
||||
this.self=self;
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@Override
|
||||
protected View onCreateContentView(Context context){
|
||||
LayoutInflater inflater=LayoutInflater.from(context);
|
||||
View content=inflater.inflate(R.layout.wrap_summary, null);
|
||||
|
||||
ImageView appIcon=content.findViewById(R.id.app_icon);
|
||||
ImageView selfAva=content.findViewById(R.id.self_ava);
|
||||
TextView selfName=content.findViewById(R.id.self_name);
|
||||
TextView mostBoostedText=content.findViewById(R.id.most_boosted_post_text);
|
||||
TextView plusFollowers=content.findViewById(R.id.plus_followers);
|
||||
TextView followersLabel=content.findViewById(R.id.followers_label);
|
||||
TextView totalFollowers=content.findViewById(R.id.followers_total);
|
||||
View followersChart=content.findViewById(R.id.followers_chart);
|
||||
TextView hashtag=content.findViewById(R.id.most_used_hashtag);
|
||||
TextView followersPercent=content.findViewById(R.id.followers_percentile);
|
||||
TextView newPostsCount=content.findViewById(R.id.new_posts);
|
||||
TextView newPostsLabel=content.findViewById(R.id.new_posts_label);
|
||||
View newPostsBlock=content.findViewById(R.id.new_posts_block);
|
||||
postIcon=prepareIcon(context, R.drawable.ic_chat_bubble_wght700_20px);
|
||||
|
||||
if(!report.mostUsedApps.isEmpty()){
|
||||
AnnualReport.NameAndCount app=report.mostUsedApps.get(0);
|
||||
if("Mastodon for Android".equals(app.name) || "Mastodon for iOS".equals(app.name)){
|
||||
appIcon.setImageResource(R.mipmap.ic_launcher);
|
||||
}else{
|
||||
String url=AppsWrapScene.getIconUrl(app.name);
|
||||
if(url==null)
|
||||
url=AppsWrapScene.getIconUrl(app.name.split(" ")[0]);
|
||||
if(url!=null){
|
||||
ViewImageLoader.loadWithoutAnimation(appIcon, null, new UrlImageLoaderRequest(Bitmap.Config.ARGB_8888, V.dp(96), V.dp(96), List.of(), Uri.parse(url)));
|
||||
}
|
||||
}
|
||||
}
|
||||
ViewImageLoader.loadWithoutAnimation(selfAva, null, new UrlImageLoaderRequest(Bitmap.Config.ARGB_8888, V.dp(40), V.dp(40), List.of(), Uri.parse(self.avatarStatic)));
|
||||
selfName.setText(HtmlParser.parseCustomEmoji(self.displayName, self.emojis));
|
||||
UiUtils.loadCustomEmojiInTextView(selfName);
|
||||
Status mostBoosted=allStatuses.get(report.topStatuses.byReblogs);
|
||||
if(mostBoosted!=null){
|
||||
mostBoostedText.setText(HtmlParser.parse(mostBoosted.content, mostBoosted.emojis, mostBoosted.mentions, mostBoosted.tags, null, mostBoosted));
|
||||
}
|
||||
int newFollowers=report.timeSeries.stream().mapToInt(p->p.followers).sum();
|
||||
plusFollowers.setText("+"+UiUtils.abbreviateNumber(newFollowers));
|
||||
followersLabel.setText(content.getResources().getQuantityString(R.plurals.followers, newFollowers>1000 ? 9999 : newFollowers));
|
||||
totalFollowers.setText(context.getString(R.string.x_followers_total, UiUtils.abbreviateNumber(self.followersCount)));
|
||||
if(!report.topHashtags.isEmpty()){
|
||||
hashtag.setText("#"+report.topHashtags.get(0).name);
|
||||
}
|
||||
double followersPercentile=Objects.requireNonNullElse(report.percentiles.get("followers"), 0.0);
|
||||
followersPercent.setText(Math.max(1, Math.round(100-followersPercentile))+"%");
|
||||
newPostsCount.setText(NumberFormat.getInstance().format(report.typeDistribution.standalone));
|
||||
newPostsLabel.setText(context.getResources().getQuantityString(R.plurals.new_posts, report.typeDistribution.standalone));
|
||||
|
||||
newPostsBlock.setBackground(new NewPostsBackgroundDrawable());
|
||||
followersChart.setBackground(new FollowersChartDrawable());
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroyContentView(){
|
||||
|
||||
}
|
||||
|
||||
private Drawable prepareIcon(Context context, int res){
|
||||
Drawable d=context.getResources().getDrawable(res, context.getTheme()).mutate();
|
||||
d.setTint(0xFF17063B);
|
||||
d.setBounds(V.dp(2), V.dp(2), V.dp(14), V.dp(14));
|
||||
return d;
|
||||
}
|
||||
|
||||
private class NewPostsBackgroundDrawable extends Drawable{
|
||||
|
||||
private static final int GRID_W=11;
|
||||
private static final int GRID_H=6;
|
||||
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas){
|
||||
Rect bounds=getBounds();
|
||||
paint.setColor(0xFF2F0C7A);
|
||||
canvas.drawRect(bounds, paint);
|
||||
float spacing=(bounds.width()+V.dp(16)-V.dp(16*GRID_W))/(float)(GRID_W-1);
|
||||
float radius=V.dp(8);
|
||||
canvas.saveLayerAlpha(bounds.left, bounds.top, bounds.right, bounds.bottom, 51);
|
||||
canvas.translate(-radius, -radius);
|
||||
for(int y=0;y<GRID_H;y++){
|
||||
canvas.save();
|
||||
for(int x=0;x<GRID_W;x++){
|
||||
int color;
|
||||
Drawable icon;
|
||||
color=0xFFFFBE2E;
|
||||
icon=postIcon;
|
||||
paint.setColor(color);
|
||||
canvas.drawCircle(radius, radius, radius, paint);
|
||||
if(icon!=null){
|
||||
icon.draw(canvas);
|
||||
}
|
||||
canvas.translate(radius*2+spacing, 0);
|
||||
}
|
||||
canvas.restore();
|
||||
canvas.translate(0, radius*2+spacing);
|
||||
}
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable ColorFilter colorFilter){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity(){
|
||||
return PixelFormat.OPAQUE;
|
||||
}
|
||||
}
|
||||
|
||||
private class FollowersChartDrawable extends Drawable{
|
||||
private Path path=new Path();
|
||||
private Paint fillPaint=new Paint(Paint.ANTI_ALIAS_FLAG), strokePaint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
public FollowersChartDrawable(){
|
||||
strokePaint.setStyle(Paint.Style.STROKE);
|
||||
strokePaint.setStrokeWidth(V.dp(1));
|
||||
strokePaint.setColor(0xFF562CFC);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas){
|
||||
canvas.drawPath(path, fillPaint);
|
||||
canvas.drawPath(path, strokePaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable ColorFilter colorFilter){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(@NonNull Rect bounds){
|
||||
path.rewind();
|
||||
int pad=V.dp(16);
|
||||
path.moveTo(-20, bounds.height()+20);
|
||||
float dx=bounds.width()/11f;
|
||||
float x=0f;
|
||||
int max=report.timeSeries.stream().mapToInt(p->p.followers).max().orElse(1);
|
||||
for(AnnualReport.TimeSeriesPoint point:report.timeSeries){
|
||||
float fraction=point.followers/(float)max;
|
||||
path.lineTo(x, pad+(bounds.height()-pad*2)*(1f-fraction));
|
||||
x+=dx;
|
||||
}
|
||||
path.lineTo(bounds.width()+20, bounds.height()+20);
|
||||
path.close();
|
||||
fillPaint.setShader(new LinearGradient(0, pad, 0, bounds.height()-pad, 0x80562CFC, 0x00562CFC, Shader.TileMode.CLAMP));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity(){
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
package org.joinmastodon.android.ui.wrapstodon;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.AnnualReport;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.util.List;
|
||||
|
||||
public class TimeSeriesWrapScene extends AnnualWrapScene{
|
||||
private final List<AnnualReport.TimeSeriesPoint> points;
|
||||
private TextView postsCounter, followersCounter, followingCounter;
|
||||
private TextView postsLabel, followersLabel, followingLabel;
|
||||
private TextView monthLabel;
|
||||
|
||||
private int currentMonth, monthOnGestureStart;
|
||||
private float downX, downY, touchslop;
|
||||
private boolean trackingTouch, figuringOutWhetherWeWantThisGesture;
|
||||
|
||||
public TimeSeriesWrapScene(List<AnnualReport.TimeSeriesPoint> points){
|
||||
this.points=points;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View onCreateContentView(Context context){
|
||||
LayoutInflater inflater=LayoutInflater.from(context);
|
||||
View content=inflater.inflate(R.layout.wrap_time_series, null);
|
||||
|
||||
postsCounter=content.findViewById(R.id.posts_counter);
|
||||
followersCounter=content.findViewById(R.id.followers_counter);
|
||||
followingCounter=content.findViewById(R.id.following_counter);
|
||||
postsLabel=content.findViewById(R.id.posts_label);
|
||||
followersLabel=content.findViewById(R.id.followers_label);
|
||||
followingLabel=content.findViewById(R.id.following_label);
|
||||
monthLabel=content.findViewById(R.id.month_label);
|
||||
setMonth(0);
|
||||
|
||||
content.setOnTouchListener(this::onContentTouch);
|
||||
touchslop=ViewConfiguration.get(context).getScaledTouchSlop();
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroyContentView(){
|
||||
|
||||
}
|
||||
|
||||
private boolean onContentTouch(View v, MotionEvent ev){
|
||||
if(ev.getAction()==MotionEvent.ACTION_DOWN){
|
||||
downX=ev.getX();
|
||||
downY=ev.getY();
|
||||
trackingTouch=false;
|
||||
figuringOutWhetherWeWantThisGesture=true;
|
||||
monthOnGestureStart=currentMonth;
|
||||
v.getParent().requestDisallowInterceptTouchEvent(true);
|
||||
}else if(ev.getAction()==MotionEvent.ACTION_MOVE){
|
||||
if(!trackingTouch){
|
||||
float dX=Math.abs(downX-ev.getX());
|
||||
float dY=Math.abs(downY-ev.getY());
|
||||
if(dX>dY && dX>=touchslop){
|
||||
trackingTouch=true;
|
||||
figuringOutWhetherWeWantThisGesture=false;
|
||||
}else if(dY>=touchslop){
|
||||
v.getParent().requestDisallowInterceptTouchEvent(false);
|
||||
figuringOutWhetherWeWantThisGesture=false;
|
||||
}
|
||||
}else{
|
||||
int monthsToAdvance=(int)((ev.getX()-downX)/(v.getWidth()/15f));
|
||||
if(monthsToAdvance!=0){
|
||||
int newCurrentMonth=Math.min(11, Math.max(0, monthOnGestureStart+monthsToAdvance));
|
||||
if(newCurrentMonth!=currentMonth){
|
||||
setMonth(newCurrentMonth);
|
||||
v.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return trackingTouch || figuringOutWhetherWeWantThisGesture;
|
||||
}
|
||||
|
||||
private void setMonth(int monthIndex){
|
||||
currentMonth=monthIndex;
|
||||
AnnualReport.TimeSeriesPoint point=points.get(monthIndex);
|
||||
postsCounter.setText(NumberFormat.getInstance().format(point.statuses));
|
||||
followersCounter.setText(NumberFormat.getInstance().format(point.followers));
|
||||
followingCounter.setText(NumberFormat.getInstance().format(point.following));
|
||||
Resources r=postsCounter.getResources();
|
||||
postsLabel.setText(r.getQuantityString(R.plurals.posts, point.statuses));
|
||||
followersLabel.setText(r.getQuantityString(R.plurals.followers, point.followers));
|
||||
followingLabel.setText(r.getQuantityString(R.plurals.following, point.following));
|
||||
monthLabel.setText(r.getStringArray(R.array.months_standalone)[monthIndex]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package org.joinmastodon.android.ui.wrapstodon;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
public class TopPostsWrapScene extends AnnualWrapScene{
|
||||
private Status mostBoosted, mostFavorited, mostReplied;
|
||||
|
||||
@Override
|
||||
protected View onCreateContentView(Context context){
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroyContentView(){
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package org.joinmastodon.android.ui.wrapstodon;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
|
||||
public class WelcomeWrapScene extends AnnualWrapScene{
|
||||
@Override
|
||||
protected View onCreateContentView(Context context){
|
||||
View view=context.getSystemService(LayoutInflater.class).inflate(R.layout.wrap_welcome, null);
|
||||
TextView title=view.findViewById(R.id.title);
|
||||
title.setText(context.getString(R.string.yearly_wrap_intro_title, year));
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroyContentView(){
|
||||
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 5.5 KiB |
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="64dp"
|
||||
android:height="64dp"
|
||||
android:viewportWidth="64"
|
||||
android:viewportHeight="64">
|
||||
<path
|
||||
android:pathData="M34.34,7.947L51.66,17.947A12,12 67.622,0 1,56.052 34.34L46.052,51.66A12,12 72.459,0 1,29.66 56.053L12.34,46.053A12,12 72.459,0 1,7.947 29.66L17.947,12.34A12,12 68.44,0 1,34.34 7.947z"
|
||||
android:fillColor="#332F0C7A"/>
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M2,22 L7,8 16,17ZM5.3,18.7 L12.35,16.2 7.8,11.65ZM14.55,12.55 L13.5,11.5 19.1,5.9Q19.9,5.1 21.025,5.1Q22.15,5.1 22.95,5.9L23.55,6.5L22.5,7.55L21.9,6.95Q21.55,6.6 21.025,6.6Q20.5,6.6 20.15,6.95ZM10.55,8.55 L9.5,7.5 10.1,6.9Q10.45,6.55 10.45,6.05Q10.45,5.55 10.1,5.2L9.45,4.55L10.5,3.5L11.15,4.15Q11.95,4.95 11.95,6.05Q11.95,7.15 11.15,7.95ZM12.55,10.55 L11.5,9.5 15.1,5.9Q15.45,5.55 15.45,5.025Q15.45,4.5 15.1,4.15L13.5,2.55L14.55,1.5L16.15,3.1Q16.95,3.9 16.95,5.025Q16.95,6.15 16.15,6.95ZM16.55,14.55 L15.5,13.5 17.1,11.9Q17.9,11.1 19.025,11.1Q20.15,11.1 20.95,11.9L22.55,13.5L21.5,14.55L19.9,12.95Q19.55,12.6 19.025,12.6Q18.5,12.6 18.15,12.95ZM5.3,18.7Z"/>
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M1.667,18.333V3.417Q1.667,2.688 2.177,2.177Q2.688,1.667 3.417,1.667H16.583Q17.312,1.667 17.823,2.177Q18.333,2.688 18.333,3.417V13.25Q18.333,13.979 17.823,14.49Q17.312,15 16.583,15H5ZM3.417,14.104 L4.271,13.25H16.583Q16.583,13.25 16.583,13.25Q16.583,13.25 16.583,13.25V3.417Q16.583,3.417 16.583,3.417Q16.583,3.417 16.583,3.417H3.417Q3.417,3.417 3.417,3.417Q3.417,3.417 3.417,3.417ZM3.417,3.417Q3.417,3.417 3.417,3.417Q3.417,3.417 3.417,3.417Q3.417,3.417 3.417,3.417Q3.417,3.417 3.417,3.417V13.25Q3.417,13.25 3.417,13.25Q3.417,13.25 3.417,13.25V14.104Z"/>
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M0.958,19.042V3.667Q0.958,2.521 1.74,1.74Q2.521,0.958 3.667,0.958H16.333Q17.479,0.958 18.26,1.74Q19.042,2.521 19.042,3.667V13Q19.042,14.146 18.26,14.927Q17.479,15.708 16.333,15.708H4.292ZM3.667,13.104 L3.771,13H16.333Q16.333,13 16.333,13Q16.333,13 16.333,13V3.667Q16.333,3.667 16.333,3.667Q16.333,3.667 16.333,3.667H3.667Q3.667,3.667 3.667,3.667Q3.667,3.667 3.667,3.667ZM3.667,3.667Q3.667,3.667 3.667,3.667Q3.667,3.667 3.667,3.667Q3.667,3.667 3.667,3.667Q3.667,3.667 3.667,3.667V13Q3.667,13 3.667,13Q3.667,13 3.667,13V13.104Z"/>
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M15.125,16.104V12.625Q15.125,11.771 14.542,11.188Q13.958,10.604 13.104,10.604H7.125L9.208,12.688L7.479,14.417L2.417,9.375L7.479,4.333L9.208,6.062L7.125,8.146H13.104Q14.979,8.146 16.281,9.448Q17.583,10.75 17.583,12.625V16.104Z"/>
|
||||
</vector>
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:src="@drawable/paper_texture"
|
||||
android:tileMode="repeat"/>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="12dp"/>
|
||||
<stroke android:color="#000" android:width="1dp"/>
|
||||
<solid android:color="#0000"/>
|
||||
</shape>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="12dp"/>
|
||||
<stroke android:color="#000" android:width="2dp"/>
|
||||
<solid android:color="#0000"/>
|
||||
</shape>
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="#FFBE2E"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item android:gravity="top">
|
||||
<shape>
|
||||
<solid android:color="#80FFFFFF"/>
|
||||
<size android:height="2dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="#562CFC"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item android:gravity="top">
|
||||
<shape>
|
||||
<solid android:color="#80FFFFFF"/>
|
||||
<size android:height="2dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="#72737C"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item android:gravity="top">
|
||||
<shape>
|
||||
<solid android:color="#80FFFFFF"/>
|
||||
<size android:height="2dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="24dp"/>
|
||||
<stroke android:width="2dp" android:color="#80FFFFFF"/>
|
||||
</shape>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="10dp"/>
|
||||
<stroke android:width="2dp" android:color="#80FFFFFF"/>
|
||||
</shape>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="4dp"/>
|
||||
<stroke android:width="2dp" android:color="#80FFFFFF"/>
|
||||
</shape>
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||
<stroke android:color="#26FFFFFF" android:width="2dp"/>
|
||||
</shape>
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||
<stroke android:color="#26FFFFFF" android:width="1dp"/>
|
||||
</shape>
|
|
@ -0,0 +1,25 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="38dp"
|
||||
android:height="77dp"
|
||||
android:viewportWidth="38"
|
||||
android:viewportHeight="77">
|
||||
<path
|
||||
android:pathData="M35,73L19,61L3,73"
|
||||
android:strokeAlpha="0.2"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#858AFA"
|
||||
android:fillAlpha="0.2"/>
|
||||
<path
|
||||
android:pathData="M35,45L19,33L3,45"
|
||||
android:strokeAlpha="0.5"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#858AFA"
|
||||
android:fillAlpha="0.5"/>
|
||||
<path
|
||||
android:pathData="M35,17L19,5L3,17"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#858AFA"/>
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:gravity="center">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="#000"/>
|
||||
<size android:width="272dp" android:height="272dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="8dp"/>
|
||||
<solid android:color="#2F0C7A"/>
|
||||
</shape>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<font-family xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fontProviderAuthority="com.google.android.gms.fonts"
|
||||
android:fontProviderPackage="com.google.android.gms"
|
||||
android:fontProviderQuery="name=Caveat&weight=700">
|
||||
</font-family>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<font-family xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fontProviderAuthority="com.google.android.gms.fonts"
|
||||
android:fontProviderPackage="com.google.android.gms"
|
||||
android:fontProviderQuery="name=Manrope&weight=400">
|
||||
</font-family>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<font-family xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fontProviderAuthority="com.google.android.gms.fonts"
|
||||
android:fontProviderPackage="com.google.android.gms"
|
||||
android:fontProviderQuery="name=Manrope&weight=500">
|
||||
</font-family>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<font-family xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fontProviderAuthority="com.google.android.gms.fonts"
|
||||
android:fontProviderPackage="com.google.android.gms"
|
||||
android:fontProviderQuery="name=Manrope&weight=600">
|
||||
</font-family>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<font-family xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fontProviderAuthority="com.google.android.gms.fonts"
|
||||
android:fontProviderPackage="com.google.android.gms"
|
||||
android:fontProviderQuery="name=Manrope&weight=700">
|
||||
</font-family>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<font-family xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fontProviderAuthority="com.google.android.gms.fonts"
|
||||
android:fontProviderPackage="com.google.android.gms"
|
||||
android:fontProviderQuery="name=Roboto Slab&weight=600">
|
||||
</font-family>
|
|
@ -0,0 +1,146 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="96dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:textSize="25dp"
|
||||
android:textColor="#BAFF3B"
|
||||
android:lineSpacingExtra="-4dp"
|
||||
android:text="@string/wrap_apps_title"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:textColor="#858AFA"
|
||||
android:textSize="16dp"
|
||||
android:lineSpacingExtra="-3dp"
|
||||
android:text="@string/wrap_apps_subtitle"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/app2_bar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="32dp"
|
||||
android:background="@drawable/wrap_app2_bar"/>
|
||||
|
||||
<org.joinmastodon.android.ui.wrapstodon.RoundedImageView
|
||||
android:id="@+id/app2_icon"
|
||||
android:layout_width="96dp"
|
||||
android:layout_height="96dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
app:cornerRadius="24dp"
|
||||
android:foreground="@drawable/wrap_app_icon_border"
|
||||
tools:src="#0f0"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app2_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/app2_icon"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:textSize="16dp"
|
||||
android:textColor="#CCCFFF"
|
||||
android:lineSpacingExtra="-3dp"
|
||||
tools:text="Mastodon for Android"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/app1_bar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="32dp"
|
||||
android:background="@drawable/wrap_app1_bar"/>
|
||||
|
||||
<org.joinmastodon.android.ui.wrapstodon.RoundedImageView
|
||||
android:id="@+id/app1_icon"
|
||||
android:layout_width="96dp"
|
||||
android:layout_height="96dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
app:cornerRadius="24dp"
|
||||
android:foreground="@drawable/wrap_app_icon_border"
|
||||
tools:src="#0f0"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app1_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/app1_icon"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:textSize="16dp"
|
||||
android:textColor="#17063B"
|
||||
android:lineSpacingExtra="-3dp"
|
||||
tools:text="Mastodon for Android"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/app3_bar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="32dp"
|
||||
android:background="@drawable/wrap_app3_bar"/>
|
||||
|
||||
<org.joinmastodon.android.ui.wrapstodon.RoundedImageView
|
||||
android:id="@+id/app3_icon"
|
||||
android:layout_width="96dp"
|
||||
android:layout_height="96dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
app:cornerRadius="24dp"
|
||||
android:foreground="@drawable/wrap_app_icon_border"
|
||||
tools:src="#0f0"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app3_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/app3_icon"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:textSize="16dp"
|
||||
android:textColor="#E0E0EB"
|
||||
android:lineSpacingExtra="-3dp"
|
||||
tools:text="Mastodon for Android"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,94 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingTop="80dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#CCCFFF"
|
||||
android:textSize="25dp"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:text="@string/yearly_wrap_archetype_title"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#858AFA"
|
||||
android:textSize="20dp"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
tools:text="@string/yearly_wrap_archetype_subtitle"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/picture_frame"
|
||||
android:layout_width="280dp"
|
||||
android:layout_height="352dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="15dp"
|
||||
android:background="@drawable/paper_texture_tiled">
|
||||
|
||||
<org.joinmastodon.android.ui.wrapstodon.RoundedFrameLayout
|
||||
android:id="@+id/photo_wrap"
|
||||
android:layout_width="248dp"
|
||||
android:layout_height="248dp">
|
||||
<ImageView
|
||||
android:id="@+id/photo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:importantForAccessibility="no"/>
|
||||
</org.joinmastodon.android.ui.wrapstodon.RoundedFrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="15dp"
|
||||
android:fontFamily="@font/caveat_w700"
|
||||
android:textSize="28dp"
|
||||
android:textColor="#222222"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
tools:text="\@username"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/domain"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/caveat_w700"
|
||||
android:textSize="20dp"
|
||||
android:textColor="#72737C"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
tools:text="\@mastodon.social"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/archetype_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="39dp"
|
||||
android:textColor="#BAFF3B"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
tools:text="Pollster"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/archetype_explanation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:textSize="16dp"
|
||||
android:textColor="#858AFA"
|
||||
tools:text="You post a lot of polls or something"/>
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,200 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingTop="32dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/rank"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:textSize="61.04dp"
|
||||
android:textColor="#FFBE2E"
|
||||
tools:text="#1"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:textColor="#858AFA"
|
||||
android:textSize="14dp"
|
||||
tools:text="47 boosts"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="185dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/rect_12dp"
|
||||
android:backgroundTint="#141218"
|
||||
android:foreground="@drawable/rect_12dp_stroke"
|
||||
android:foregroundTint="#49454F">
|
||||
|
||||
<org.joinmastodon.android.ui.wrapstodon.RoundedImageView
|
||||
android:id="@+id/cover"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="81dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:scaleType="centerCrop"
|
||||
app:cornerRadius="13dp"
|
||||
app:roundBottomCorners="false"
|
||||
tools:src="#0f0"/>
|
||||
|
||||
<org.joinmastodon.android.ui.wrapstodon.RoundedImageView
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="69dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:cornerRadius="13dp"
|
||||
android:foreground="@drawable/rect_12dp_stroke2dp"
|
||||
android:foregroundTint="#141218"
|
||||
tools:src="#f00"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:layout_below="@id/cover"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:textColor="#E6E0E9"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:textSize="14dp"
|
||||
android:gravity="center_vertical"
|
||||
tools:text="Cody Fisher"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="16dp"
|
||||
android:layout_alignLeft="@id/name"
|
||||
android:layout_alignRight="@id/name"
|
||||
android:layout_below="@id/name"
|
||||
android:textSize="12dp"
|
||||
android:textColor="#CAC4D0"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
tools:text="\@cfisher@mastodon.social"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/posts_counter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_below="@id/avatar"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/posts_value"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:textSize="14dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:textColor="#E6E0E9"
|
||||
android:gravity="center_vertical"
|
||||
android:singleLine="true"
|
||||
tools:text="109"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/posts_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="16dp"
|
||||
android:textSize="11dp"
|
||||
android:textColor="#CAC4D0"
|
||||
android:gravity="center_vertical"
|
||||
android:singleLine="true"
|
||||
tools:text="posts"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/followers_counter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_below="@id/avatar"
|
||||
android:layout_toEndOf="@id/posts_counter"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/followers_value"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:textSize="14dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:textColor="#E6E0E9"
|
||||
android:gravity="center_vertical"
|
||||
android:singleLine="true"
|
||||
tools:text="109"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/followers_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="16dp"
|
||||
android:textSize="11dp"
|
||||
android:textColor="#CAC4D0"
|
||||
android:gravity="center_vertical"
|
||||
android:singleLine="true"
|
||||
tools:text="posts"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/following_counter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_below="@id/avatar"
|
||||
android:layout_toEndOf="@id/followers_counter"
|
||||
android:layout_marginTop="12dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/following_value"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:textSize="14dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:textColor="#E6E0E9"
|
||||
android:gravity="center_vertical"
|
||||
android:singleLine="true"
|
||||
tools:text="109"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/following_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="16dp"
|
||||
android:textSize="11dp"
|
||||
android:textColor="#CAC4D0"
|
||||
android:gravity="center_vertical"
|
||||
android:singleLine="true"
|
||||
tools:text="posts"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="96dp"
|
||||
android:paddingHorizontal="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="31.25dp"
|
||||
android:lineSpacingExtra="-5dp"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:textColor="#858AFA"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="20dp"
|
||||
android:lineSpacingExtra="-3dp"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:textColor="#858AFA"/>
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,104 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="96dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:text="@string/wrap_interacted_accounts_title"
|
||||
android:textColor="#BAFF3B"
|
||||
android:textSize="25dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:text="@string/wrap_interacted_accounts_subtitle"
|
||||
android:textColor="#858AFA"
|
||||
android:textSize="20dp" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/orbit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="449dp">
|
||||
|
||||
<View
|
||||
android:id="@+id/orbit_bg"
|
||||
android:layout_width="449dp"
|
||||
android:layout_height="449dp"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/inner_ring"
|
||||
android:layout_width="296dp"
|
||||
android:layout_height="296dp"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/outer_ring"
|
||||
android:layout_width="449dp"
|
||||
android:layout_height="449dp"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
<org.joinmastodon.android.ui.wrapstodon.RoundedImageView
|
||||
android:id="@+id/self_ava"
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="160dp"
|
||||
android:layout_gravity="center"
|
||||
android:foreground="@drawable/wrap_ava_border"
|
||||
app:cornerRadius="80dp"
|
||||
tools:src="#0f0" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/details_overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="164dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/details_number"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:textSize="76.29dp"
|
||||
android:textColor="#BAFF3B"
|
||||
android:singleLine="true"
|
||||
tools:text="97"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/details_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="-15dp"
|
||||
android:gravity="center"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:textColor="#858AFA"
|
||||
android:textSize="16dp"
|
||||
tools:text="replies exchanged with Theresa Webb"/>
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/wrap_numbers_bg_circle">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/number"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:textSize="131dp"
|
||||
android:textColor="#17063B"
|
||||
android:singleLine="true"
|
||||
android:gravity="center_horizontal"
|
||||
android:letterSpacing="-0.06"
|
||||
android:fontFamily="@font/manrope_w700"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/top_text"
|
||||
android:layout_width="272dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_above="@id/number"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginBottom="-14dp"
|
||||
android:textSize="14dp"
|
||||
android:textColor="#17063B"
|
||||
android:gravity="center_horizontal"
|
||||
android:fontFamily="@font/manrope_w400"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bottom_text"
|
||||
android:layout_width="272dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/number"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="-6dp"
|
||||
android:textSize="17dp"
|
||||
android:textColor="#17063B"
|
||||
android:gravity="center_horizontal"
|
||||
android:fontFamily="@font/manrope_w600"/>
|
||||
|
||||
</RelativeLayout>
|
|
@ -0,0 +1,306 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:paddingTop="96dp"
|
||||
android:paddingHorizontal="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:textSize="25dp"
|
||||
android:lineSpacingExtra="-4dp"
|
||||
android:textColor="#BAFF3B"
|
||||
android:text="@string/wrap_summary_title"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/title"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textColor="#CCCFFF"
|
||||
android:lineSpacingExtra="-3dp"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:text="@string/wrap_summary_subtitle"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/most_used_app_block"
|
||||
android:layout_width="104dp"
|
||||
android:layout_height="113dp"
|
||||
android:layout_below="@id/subtitle"
|
||||
android:layout_alignParentStart="true"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="10dp"
|
||||
android:background="@drawable/wrap_summary_block_bg">
|
||||
|
||||
<org.joinmastodon.android.ui.wrapstodon.RoundedImageView
|
||||
android:id="@+id/app_icon"
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="52dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:foreground="@drawable/wrap_app_icon_border_small"
|
||||
app:cornerRadius="4dp"
|
||||
tools:src="#f00"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:textColor="#CCCFFF"
|
||||
android:textSize="14dp"
|
||||
android:lineSpacingExtra="-2dp"
|
||||
android:text="@string/wrap_most_used_app"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<org.joinmastodon.android.ui.wrapstodon.RoundedFrameLayout
|
||||
android:id="@+id/followers_stats_block"
|
||||
android:layout_width="104dp"
|
||||
android:layout_height="127dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@id/most_used_app_block"
|
||||
android:layout_marginVertical="8dp"
|
||||
app:cornerRadius="4dp"
|
||||
android:background="@drawable/wrap_summary_block_bg">
|
||||
|
||||
<View
|
||||
android:id="@+id/followers_chart"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/plus_followers"
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:layout_marginTop="37dp"
|
||||
android:singleLine="true"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:textSize="31.25dp"
|
||||
android:textColor="#BAFF3B"
|
||||
tools:text="+1.2K"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_margin="8dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/followers_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#CCCFFF"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:textSize="14dp"
|
||||
android:singleLine="true"
|
||||
tools:text="followers"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/followers_total"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#858AFA"
|
||||
android:textSize="10.24dp"
|
||||
android:fontFamily="@font/manrope_w400"
|
||||
android:singleLine="true"
|
||||
tools:text="21.5K total"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</org.joinmastodon.android.ui.wrapstodon.RoundedFrameLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/most_boosted_post_block"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/subtitle"
|
||||
android:layout_toEndOf="@id/most_used_app_block"
|
||||
android:layout_alignBottom="@id/followers_stats_block"
|
||||
android:layout_marginStart="8dp"
|
||||
android:background="@drawable/wrap_summary_block_bg"
|
||||
android:padding="16dp">
|
||||
|
||||
<org.joinmastodon.android.ui.wrapstodon.RoundedImageView
|
||||
android:id="@+id/self_ava"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:cornerRadius="10dp"
|
||||
android:foreground="@drawable/wrap_app_icon_border_medium"
|
||||
tools:src="#00f"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/self_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:layout_toEndOf="@id/self_ava"
|
||||
android:textColor="#CCCFFF"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:textSize="14dp"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
tools:text="Cody Fisher"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:layout_toEndOf="@id/self_ava"
|
||||
android:layout_below="@id/self_name"
|
||||
android:textColor="#858AFA"
|
||||
android:textSize="14dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/wrap_most_boosted_post"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/most_boosted_post_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/self_ava"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textColor="#CCCFFF"
|
||||
android:textSize="16dp"
|
||||
android:lineSpacingExtra="5dp"
|
||||
android:ellipsize="end"
|
||||
tools:text="Post text goes here"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/most_used_hashtag_block"
|
||||
android:layout_width="216dp"
|
||||
android:layout_height="75dp"
|
||||
android:layout_below="@id/followers_stats_block"
|
||||
android:background="@drawable/wrap_summary_block_bg">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/most_used_hashtag"
|
||||
android:layout_width="500dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="-8dp"
|
||||
android:gravity="center"
|
||||
android:textSize="48.83dp"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:textColor="#CCCFFF"
|
||||
android:singleLine="true"
|
||||
tools:text="#caturday"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal|bottom"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:singleLine="true"
|
||||
android:textSize="14dp"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:textColor="#858AFA"
|
||||
android:text="@string/wrap_most_used_hashtag"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<org.joinmastodon.android.ui.wrapstodon.RoundedFrameLayout
|
||||
android:id="@+id/new_posts_block"
|
||||
android:layout_width="216dp"
|
||||
android:layout_height="127dp"
|
||||
android:layout_below="@id/most_used_hashtag_block"
|
||||
android:layout_marginTop="8dp"
|
||||
app:cornerRadius="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/new_posts"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:singleLine="true"
|
||||
android:textSize="76.29dp"
|
||||
android:textColor="#FFC954"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
tools:text="313"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/new_posts_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginBottom="13dp"
|
||||
android:textSize="20dp"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:gravity="center_horizontal"
|
||||
android:textColor="#CCCFFF"
|
||||
android:singleLine="true"
|
||||
tools:text="new posts"/>
|
||||
|
||||
</org.joinmastodon.android.ui.wrapstodon.RoundedFrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/followers_stats_block"
|
||||
android:layout_toEndOf="@id/most_used_hashtag_block"
|
||||
android:layout_alignBottom="@id/new_posts_block"
|
||||
android:layout_marginStart="8dp"
|
||||
android:paddingVertical="8dp"
|
||||
android:background="@drawable/wrap_summary_block_bg">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="7dp"
|
||||
android:textColor="#858AFA"
|
||||
android:textSize="10.24dp"
|
||||
android:fontFamily="@font/manrope_w400"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/wrap_percentile_before"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/followers_percentile"
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center_horizontal"
|
||||
android:textColor="#FFC954"
|
||||
android:textSize="61.04dp"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
tools:text="2%"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginHorizontal="7dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#858AFA"
|
||||
android:textSize="10.24dp"
|
||||
android:fontFamily="@font/manrope_w400"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/wrap_percentile_after"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#858AFA"
|
||||
android:textSize="8.19dp"
|
||||
android:fontFamily="@font/manrope_w400"
|
||||
android:gravity="center_horizontal"
|
||||
android:alpha="0.5"
|
||||
android:text="@string/wrap_percentile_after_after"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</RelativeLayout>
|
|
@ -0,0 +1,96 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="96dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:textColor="#858AFA"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:textSize="25dp"
|
||||
android:text="@string/wrap_numbers_title"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/posts_counter"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="92dp"
|
||||
android:layout_marginTop="22dp"
|
||||
android:singleLine="true"
|
||||
android:gravity="center"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:textSize="76.29dp"
|
||||
android:textColor="#FFC954"
|
||||
tools:text="12,345"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/posts_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:gravity="center"
|
||||
android:textSize="20dp"
|
||||
android:fontFamily="@font/manrope_w400"
|
||||
android:textColor="#858AFA"
|
||||
tools:text="posts"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/followers_counter"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="92dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:singleLine="true"
|
||||
android:gravity="center"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:textSize="76.29dp"
|
||||
android:textColor="#FFC954"
|
||||
tools:text="12,345"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/followers_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:gravity="center"
|
||||
android:textSize="20dp"
|
||||
android:fontFamily="@font/manrope_w400"
|
||||
android:textColor="#858AFA"
|
||||
tools:text="followers"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/following_counter"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="92dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:singleLine="true"
|
||||
android:gravity="center"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:textSize="76.29dp"
|
||||
android:textColor="#FFC954"
|
||||
tools:text="12,345"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/following_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:gravity="center"
|
||||
android:textSize="20dp"
|
||||
android:fontFamily="@font/manrope_w400"
|
||||
android:textColor="#858AFA"
|
||||
tools:text="following"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/month_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12.8dp"
|
||||
android:textColor="#FFBE2E"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
tools:text="January"/>
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<ImageButton
|
||||
android:id="@+id/btn_back"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_margin="16dp"
|
||||
android:layout_gravity="top|start"
|
||||
android:tint="?colorM3OnSurfaceVariant"
|
||||
android:background="@drawable/bg_button_m3_tonal_icon"
|
||||
android:outlineProvider="background"
|
||||
android:contentDescription="@string/back"
|
||||
android:src="@drawable/ic_baseline_close_24"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_share"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_gravity="top|end"
|
||||
android:drawableStart="@drawable/ic_share_20px"
|
||||
android:drawablePadding="8dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="24dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled"
|
||||
android:drawableTint="?colorM3OnPrimary"
|
||||
android:text="@string/button_share"/>
|
||||
</merge>
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.joinmastodon.android.ui.views.NestableScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
</LinearLayout>
|
||||
|
||||
</org.joinmastodon.android.ui.views.NestableScrollView>
|
|
@ -0,0 +1,54 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="14dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:textSize="20dp"
|
||||
android:textColor="#858AFA"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:includeFontPadding="false"
|
||||
tools:text="Welcome to your 2023"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:textSize="48.83dp"
|
||||
android:textColor="#F0F0F0"
|
||||
android:fontFamily="@font/manrope_w600"
|
||||
android:includeFontPadding="false"
|
||||
android:text="#Wrapstodon"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:textSize="20dp"
|
||||
android:textColor="#CCCFFF"
|
||||
android:fontFamily="@font/manrope_w500"
|
||||
android:includeFontPadding="false"
|
||||
android:text="@string/yearly_wrap_intro_text"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="38dp"
|
||||
android:layout_height="77dp"
|
||||
android:layout_marginBottom="48dp"
|
||||
android:layout_gravity="bottom|center_horizontal"
|
||||
android:background="@drawable/wrap_intro_chevrons"/>
|
||||
|
||||
</FrameLayout>
|
|
@ -59,4 +59,9 @@
|
|||
<attr name="aspectRatio" format="float"/>
|
||||
<attr name="useHeight" format="boolean"/>
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="RoundedImageView">
|
||||
<attr name="cornerRadius" format="dimension"/>
|
||||
<attr name="roundBottomCorners" format="boolean"/>
|
||||
</declare-styleable>
|
||||
</resources>
|
|
@ -679,4 +679,68 @@
|
|||
<string name="this_invite_has_expired">This invite link has expired.</string>
|
||||
<string name="invite_link_pasted">Link pasted from your clipboard.</string>
|
||||
<string name="need_invite_to_join_server">To join %s, you’ll need an invite link from an existing user.</string>
|
||||
<string name="yearly_wrap_title">Your %s #Wrapstodon awaits!</string>
|
||||
<string name="yearly_wrap_text">Unveil your year’s highlights and memorable moments on Mastodon.</string>
|
||||
<string name="yearly_wrap_view">View #Wrapstodon</string>
|
||||
<string name="yearly_wrap_intro_title">Welcome to your %s</string>
|
||||
<string name="yearly_wrap_intro_text">Let’s take a minute to celebrate your past year on Mastodon.</string>
|
||||
<string name="yearly_wrap_archetype_title">Vibe check!</string>
|
||||
<string name="yearly_wrap_archetype_subtitle">Your %s Mastodon archetype is:</string>
|
||||
<string name="yearly_wrap_archetype_lurker">Lurker</string>
|
||||
<string name="yearly_wrap_archetype_booster">Booster</string>
|
||||
<string name="yearly_wrap_archetype_replier">Replier</string>
|
||||
<string name="yearly_wrap_archetype_pollster">Pollster</string>
|
||||
<string name="yearly_wrap_archetype_oracle">Oracle</string>
|
||||
<string name="wrap_numbers_total_title">you posted, replied, and boosted</string>
|
||||
<string name="wrap_numbers_boosts_title">%,d, or</string>
|
||||
<string name="wrap_numbers_replies_title">...%,d, or</string>
|
||||
<string name="wrap_numbers_standalone_posts_title">...and %,d, or</string>
|
||||
<string name="wrap_numbers_x_times_in_year">times in %s.</string>
|
||||
<string name="wrap_numbers_boosts">were boosts...</string>
|
||||
<string name="wrap_numbers_replies">were replies...</string>
|
||||
<string name="wrap_numbers_standalone_posts">were new posts.</string>
|
||||
<string name="wrap_most_reblogged_title">Those were the fan favorites. But what about <b>your favorites</b>?</string>
|
||||
<string name="wrap_most_reblogged_subtitle">These are accounts you <b>boosted</b> the most in %s:</string>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">post</item>
|
||||
<item quantity="other">posts</item>
|
||||
</plurals>
|
||||
<string name="wrap_favorite_tags_title">What about your <b>favorite hashtags</b>?</string>
|
||||
<string name="wrap_favorite_tags_subtitle">Discover your most posted, liked, and boosted hashtags.</string>
|
||||
<string name="wrap_interacted_accounts_title">In your orbit</string>
|
||||
<string name="wrap_interacted_accounts_subtitle">These are the people you replied to the most over the past year.</string>
|
||||
<string name="wrap_apps_title">Mastodon is nothing without amazing apps.</string>
|
||||
<string name="wrap_apps_subtitle">It should come as no surprise, but here are your favorites:</string>
|
||||
<string name="wrap_numbers_title">It’s been a big year for you.</string>
|
||||
<string name="wrap_summary_title">Thanks for being part of Mastodon!</string>
|
||||
<string name="wrap_summary_subtitle">That’s all for now. Share your stats to your followers:</string>
|
||||
<string name="wrap_most_used_app">most used app</string>
|
||||
<string name="wrap_most_boosted_post">most boosted post</string>
|
||||
<string name="wrap_most_used_hashtag">most used hashtag</string>
|
||||
<string name="wrap_percentile_before">That puts you in the top</string>
|
||||
<string name="wrap_percentile_after">of Mastodon users.</string>
|
||||
<string name="wrap_percentile_after_after">We won’t tell Bernie.</string>
|
||||
<string name="x_followers_total">%s total</string>
|
||||
<plurals name="wrap_replies_exchanged">
|
||||
<item quantity="one">reply exchanged with %s</item>
|
||||
<item quantity="other">replies exchanged with %s</item>
|
||||
</plurals>
|
||||
<string-array name="months_standalone">
|
||||
<item>January</item>
|
||||
<item>February</item>
|
||||
<item>March</item>
|
||||
<item>April</item>
|
||||
<item>May</item>
|
||||
<item>June</item>
|
||||
<item>July</item>
|
||||
<item>August</item>
|
||||
<item>September</item>
|
||||
<item>October</item>
|
||||
<item>November</item>
|
||||
<item>December</item>
|
||||
</string-array>
|
||||
<plurals name="new_posts">
|
||||
<item quantity="one">new post</item>
|
||||
<item quantity="other">new posts</item>
|
||||
</plurals>
|
||||
</resources>
|
Loading…
Reference in New Issue