mastodon-app-ufficiale-android/mastodon/src/main/java/org/joinmastodon/android/fragments/WrapstodonFragment.java

323 lines
11 KiB
Java

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));
}
}
}