diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/instance/GetDomainBlocks.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/instance/GetDomainBlocks.java new file mode 100644 index 000000000..e2c8aa9cf --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/instance/GetDomainBlocks.java @@ -0,0 +1,16 @@ +package org.joinmastodon.android.api.requests.instance; + +import com.google.gson.reflect.TypeToken; + +import org.joinmastodon.android.api.MastodonAPIRequest; +import org.joinmastodon.android.model.DomainBlock; +import org.joinmastodon.android.model.ExtendedDescription; + +import java.util.List; + +public class GetDomainBlocks extends MastodonAPIRequest>{ + public GetDomainBlocks(){ + super(HttpMethod.GET, "/instance/domain_blocks", new TypeToken<>(){}); + } + +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/instance/GetExtendedDescription.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/instance/GetExtendedDescription.java new file mode 100644 index 000000000..5ce739a62 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/instance/GetExtendedDescription.java @@ -0,0 +1,12 @@ +package org.joinmastodon.android.api.requests.instance; + +import org.joinmastodon.android.api.MastodonAPIRequest; +import org.joinmastodon.android.model.ExtendedDescription; +import org.joinmastodon.android.model.Instance; + +public class GetExtendedDescription extends MastodonAPIRequest{ + public GetExtendedDescription(){ + super(HttpMethod.GET, "/instance/extended_description", ExtendedDescription.class); + } + +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/instance/GetWeeklyActivity.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/instance/GetWeeklyActivity.java new file mode 100644 index 000000000..87f74f9de --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/instance/GetWeeklyActivity.java @@ -0,0 +1,15 @@ +package org.joinmastodon.android.api.requests.instance; + +import com.google.gson.reflect.TypeToken; + +import org.joinmastodon.android.api.MastodonAPIRequest; +import org.joinmastodon.android.model.WeeklyActivity; + +import java.util.List; + +public class GetWeeklyActivity extends MastodonAPIRequest>{ + public GetWeeklyActivity(){ + super(HttpMethod.GET, "/instance/activity", new TypeToken<>(){}); + } + +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/InstanceBlockListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/InstanceBlockListFragment.java new file mode 100644 index 000000000..4aa6fa1d1 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/InstanceBlockListFragment.java @@ -0,0 +1,185 @@ +package org.joinmastodon.android.fragments; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.os.Build; +import android.os.Bundle; +import android.text.Html; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowInsets; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import org.joinmastodon.android.R; +import org.joinmastodon.android.api.requests.instance.GetDomainBlocks; +import org.joinmastodon.android.fragments.onboarding.GoogleMadeMeAddThisFragment; +import org.joinmastodon.android.model.DomainBlock; +import org.joinmastodon.android.model.Instance; +import org.joinmastodon.android.model.Severity; +import org.joinmastodon.android.ui.DividerItemDecoration; +import org.joinmastodon.android.ui.text.HtmlParser; +import org.joinmastodon.android.ui.utils.UiUtils; +import org.joinmastodon.android.utils.ElevationOnScrollListener; +import org.parceler.Parcels; + +import java.util.List; + +import me.grishka.appkit.Nav; +import me.grishka.appkit.api.SimpleCallback; +import me.grishka.appkit.fragments.LoaderFragment; +import me.grishka.appkit.fragments.ToolbarFragment; +import me.grishka.appkit.utils.BindableViewHolder; +import me.grishka.appkit.utils.MergeRecyclerAdapter; +import me.grishka.appkit.utils.SingleViewRecyclerAdapter; +import me.grishka.appkit.utils.V; +import me.grishka.appkit.views.FragmentRootLinearLayout; +import me.grishka.appkit.views.UsableRecyclerView; + +public class InstanceBlockListFragment extends LoaderFragment { + private UsableRecyclerView list; + private MergeRecyclerAdapter adapter; + private View buttonBar; + private Instance instance; + private ElevationOnScrollListener onScrollListener; + + private List domainBlocks; + + @Override + public void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + setRetainInstance(true); + loadData(); + } + + @Override + public void onAttach(Activity activity){ + super.onAttach(activity); + setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground)); + instance=Parcels.unwrap(getArguments().getParcelable("instance")); + setTitle(R.string.mo_instance_info_moderated_servers); + } + + @Override + public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){ + View view=inflater.inflate(R.layout.fragment_onboarding_rules, container, false); + + list=view.findViewById(R.id.list); + list.setLayoutManager(new LinearLayoutManager(getActivity())); + + adapter=new MergeRecyclerAdapter(); + adapter.addAdapter(new ItemsAdapter()); + list.setAdapter(adapter); + list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3SurfaceVariant, 1, 0, 0, DividerItemDecoration.NOT_FIRST)); + + buttonBar=view.findViewById(R.id.button_bar); + + view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this)); + + return view; + } + + @Override + protected void doLoadData() { + currentRequest= new GetDomainBlocks().setCallback(new SimpleCallback<>(this) { + @Override + public void onSuccess(List result) { + domainBlocks=result; + dataLoaded(); + } + }).execNoAuth(instance.uri); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState){ + super.onViewCreated(view, savedInstanceState); + setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background)); + view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background)); + list.addOnScrollListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar())); + } + + @Override + protected void onUpdateToolbar(){ + super.onUpdateToolbar(); + getToolbar().setBackgroundResource(R.drawable.bg_onboarding_panel); + getToolbar().setElevation(0); + if(onScrollListener!=null){ + onScrollListener.setViews(buttonBar, getToolbar()); + } + } + + @Override + public void onApplyWindowInsets(WindowInsets insets){ + if(Build.VERSION.SDK_INT>=27){ + int inset=insets.getSystemWindowInsetBottom(); + buttonBar.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0); + super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), 0)); + }else{ + super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom())); + } + } + + + @Override + public void onRefresh(){ + doLoadData(); + } + private class ItemsAdapter extends RecyclerView.Adapter{ + + @NonNull + @Override + public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){ + return new ItemViewHolder(); + } + + @Override + public void onBindViewHolder(@NonNull ItemViewHolder holder, int position){ + holder.bind(domainBlocks.get(position)); + } + + @Override + public int getItemCount(){ + return domainBlocks.size(); + } + } + + private class ItemViewHolder extends BindableViewHolder{ + private final TextView instanceUri, reason; + private final ImageView severity; + + public ItemViewHolder(){ + super(getActivity(), R.layout.item_server_block, list); + instanceUri=findViewById(R.id.instance); + reason=findViewById(R.id.reason); + severity=findViewById(R.id.severity); + } + + @SuppressLint("DefaultLocale") + @Override + public void onBind(DomainBlock item){ + instanceUri.setText(item.domain); + reason.setText(item.comment); + switch (item.severity) { + case SILENCE -> { + severity.setImageDrawable(getContext().getDrawable(R.drawable.ic_fluent_speaker_mute_28_regular)); + severity.setContentDescription(getContext().getString(R.string.mo_severity_silence)); + } + case SUSPEND -> { + severity.setImageDrawable(getContext().getDrawable(R.drawable.ic_fluent_shield_prohibited_28_regular)); + severity.setContentDescription(getContext().getString(R.string.mo_severity_suspend)); + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + severity.setTooltipText(severity.getContentDescription()); + } + } + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/InstanceInfoFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/InstanceInfoFragment.java new file mode 100644 index 000000000..180c3b899 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/InstanceInfoFragment.java @@ -0,0 +1,437 @@ +package org.joinmastodon.android.fragments; + +import android.app.Activity; +import android.content.res.Configuration; +import android.graphics.Outline; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Bundle; +import android.text.SpannableStringBuilder; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewOutlineProvider; +import android.view.WindowInsets; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import org.joinmastodon.android.DomainManager; +import org.joinmastodon.android.R; +import org.joinmastodon.android.api.requests.instance.GetExtendedDescription; +import org.joinmastodon.android.api.requests.instance.GetInstance; +import org.joinmastodon.android.api.session.AccountSessionManager; +import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment; +import org.joinmastodon.android.model.Account; +import org.joinmastodon.android.model.AccountField; +import org.joinmastodon.android.model.Attachment; +import org.joinmastodon.android.model.ExtendedDescription; +import org.joinmastodon.android.model.Instance; +import org.joinmastodon.android.ui.BetterItemAnimator; +import org.joinmastodon.android.ui.SingleImagePhotoViewerListener; +import org.joinmastodon.android.ui.drawables.CoverOverlayGradientDrawable; +import org.joinmastodon.android.ui.photoviewer.PhotoViewer; +import org.joinmastodon.android.ui.text.HtmlParser; +import org.joinmastodon.android.ui.utils.UiUtils; +import org.joinmastodon.android.ui.views.CoverImageView; +import org.joinmastodon.android.ui.views.LinkedTextView; +import org.parceler.Parcels; + +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import me.grishka.appkit.Nav; +import me.grishka.appkit.api.SimpleCallback; +import me.grishka.appkit.fragments.LoaderFragment; +import me.grishka.appkit.imageloader.ListImageLoaderWrapper; +import me.grishka.appkit.imageloader.RecyclerViewDelegate; +import me.grishka.appkit.imageloader.ViewImageLoader; +import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; +import me.grishka.appkit.utils.BindableViewHolder; +import me.grishka.appkit.views.UsableRecyclerView; + +public class InstanceInfoFragment extends LoaderFragment { + + private Instance instance; + private String extendedDescription; + private CoverImageView cover; + private TextView uri, description, readMore; + private SwipeRefreshLayout refreshLayout; + private final CoverOverlayGradientDrawable coverGradient=new CoverOverlayGradientDrawable(); + private LinearLayout textWrap; + + private ScrollView scrollView, textScrollView; + private float titleTransY; + + private String accountID; + private Account account; + private String targetDomain; + private final ArrayList fields=new ArrayList<>(); + + private boolean refreshing; + private boolean isExpanded = false; + + public UsableRecyclerView list; + private List metadataListData=Collections.emptyList(); + private MetadataAdapter adapter; + private ListImageLoaderWrapper imgLoader; + + private float textMaxHeight, textCollapsedHeight; + private LinearLayout.LayoutParams collapseParams, wrapParams; + + public InstanceInfoFragment(){ + super(R.layout.loader_fragment_overlay_toolbar); + } + + @Override + public void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N) + setRetainInstance(true); + + accountID=getArguments().getString("account"); + account= AccountSessionManager.getInstance().getAccount(accountID).self; + targetDomain=getArguments().getString("instanceDomain"); + loadData(); + loadExtendedDescription(); + DomainManager.getInstance().setCurrentDomain(targetDomain + "/about"); + } + + @Override + public void onAttach(Activity activity){ + super.onAttach(activity); + setHasOptionsMenu(true); + } + + @Override + public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){ + View content=inflater.inflate(R.layout.fragment_instance_info, container, false); + + refreshLayout=content.findViewById(R.id.refresh_layout); + scrollView=content.findViewById(R.id.scroll_view); + cover=content.findViewById(R.id.cover); + uri =content.findViewById(R.id.uri); + description=content.findViewById(R.id.description); + list=content.findViewById(R.id.metadata); + textScrollView=content.findViewById(R.id.text_scroll_view); + textWrap=content.findViewById(R.id.text_wrap); + readMore=content.findViewById(R.id.read_more); + textMaxHeight=getActivity().getResources().getDimension(R.dimen.description_max_height); + textCollapsedHeight=getActivity().getResources().getDimension(R.dimen.description_collapsed_height); + collapseParams=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) textCollapsedHeight); + wrapParams=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + + cover.setForeground(coverGradient); + cover.setOnClickListener(this::onCoverClick); + readMore.setOnClickListener(this::onReadMoreClick); + refreshLayout.setOnRefreshListener(this); + + + if(loaded){ + bindViews(); + dataLoaded(); + } + + list.setItemAnimator(new BetterItemAnimator()); + list.setDrawSelectorOnTop(true); + list.setLayoutManager(new LinearLayoutManager(getActivity())); + imgLoader=new ListImageLoaderWrapper(getActivity(), list, new RecyclerViewDelegate(list), null); + list.setAdapter(adapter=new MetadataAdapter()); + list.setClipToPadding(false); + + return content; + } + + @Override + protected void doLoadData(){ + currentRequest=new GetInstance() + .setCallback(new SimpleCallback<>(this){ + @Override + public void onSuccess(Instance result){ + if (getActivity() == null) return; + instance = result; + bindViews(); + dataLoaded(); + if(refreshing) { + refreshing = false; + refreshLayout.setRefreshing(false); + } + } + }) + .execNoAuth(targetDomain); + } + + private void loadExtendedDescription() { + new GetExtendedDescription() + .setCallback(new SimpleCallback<>(this){ + @Override + public void onSuccess(ExtendedDescription result){ + if (getActivity() == null || result == null || TextUtils.isEmpty(result.content)) return; + extendedDescription = result.content; + updateDescription(); + collapseDescription(); + } + }) + .execNoAuth(targetDomain); + } + + private void updateDescription() { + if (instance == null || description == null) + return; + + description.setText(HtmlParser.parse(TextUtils.isEmpty(extendedDescription) ? + TextUtils.isEmpty(instance.description) ? instance.shortDescription : instance.description + : extendedDescription, + account.emojis, Collections.emptyList(), Collections.emptyList(), accountID)); + + description.measure( + View.MeasureSpec.makeMeasureSpec(textWrap.getWidth(), View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); + } + + @Override + public void onRefresh(){ + if(refreshing) + return; + refreshing=true; + doLoadData(); + loadExtendedDescription(); + } + + @Override + public void dataLoaded(){ + if(getActivity()==null) + return; + setFields(fields); + super.dataLoaded(); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState){ + super.onViewCreated(view, savedInstanceState); + updateToolbar(); + titleTransY=getToolbar().getLayoutParams().height; + if(toolbarTitleView!=null){ + toolbarTitleView.setTranslationY(titleTransY); + toolbarSubtitleView.setTranslationY(titleTransY); + } + } + + @Override + public void onConfigurationChanged(Configuration newConfig){ + super.onConfigurationChanged(newConfig); + updateToolbar(); + } + + @Override + public void onApplyWindowInsets(WindowInsets insets){ + int statusBarHeight = insets.getSystemWindowInsetTop(); + if(contentView!=null){ + ((ViewGroup.MarginLayoutParams) getToolbar().getLayoutParams()).topMargin= statusBarHeight; + } + super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom())); + } + + + + private void bindViews(){ + ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(instance.thumbnail, 1000, 1000)); + uri.setText(instance.title); + setTitle(instance.title); + + updateDescription(); + collapseDescription(); + + fields.clear(); + + + if (instance.contactAccount != null) { + AccountField admin = new AccountField(); + admin.parsedName=admin.name=getContext().getString(R.string.mo_instance_admin); + admin.parsedValue=buildLinkText(instance.contactAccount.url, instance.contactAccount.getDisplayUsername() + "@" + instance.uri); + fields.add(admin); + } + + if (instance.email != null) { + AccountField contact = new AccountField(); + contact.parsedName=getContext().getString(R.string.mo_instance_contact); + contact.parsedValue=buildLinkText("mailto:" + instance.email, instance.email); + fields.add(contact); + } + + if (instance.stats != null) { + AccountField activeUsers = new AccountField(); + activeUsers.parsedName=getContext().getString(R.string.mo_instance_users); + activeUsers.parsedValue= NumberFormat.getInstance().format(instance.stats.userCount); + fields.add(activeUsers); + + AccountField statusCount = new AccountField(); + statusCount.parsedName=getContext().getString(R.string.mo_instance_status); + statusCount.parsedValue= NumberFormat.getInstance().format(instance.stats.statusCount); + fields.add(statusCount); + } + + AccountField registration = new AccountField(); + registration.parsedName=getContext().getString(R.string.mo_instance_registration); + registration.parsedValue=getContext().getString(instance.registrations ? instance.approvalRequired ? R.string.mo_instance_registration_approval : R.string.mo_instance_registration_open : R.string.instance_signup_closed); + fields.add(registration); + + setFields(fields); + } + + private SpannableStringBuilder buildLinkText(String link, String text) { + String value = "" + text + ""; + return HtmlParser.parse(value, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID); + } + + private void collapseDescription() { + textScrollView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)); + + description.measure( + View.MeasureSpec.makeMeasureSpec(textWrap.getWidth(), View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); + + readMore.setText(isExpanded ? R.string.sk_collapse : R.string.sk_expand); + description.post(() -> { + boolean tooBig = description.getMeasuredHeight() > textMaxHeight; + readMore.setVisibility(tooBig ? View.VISIBLE : View.GONE); + textScrollView.setLayoutParams(tooBig && !isExpanded ? collapseParams : wrapParams); + }); + } + + private void updateToolbar(){ + getToolbar().setBackgroundColor(0); + if(toolbarTitleView!=null){ + toolbarTitleView.setTranslationY(titleTransY); + toolbarSubtitleView.setTranslationY(titleTransY); + } + getToolbar().setOnClickListener(v->scrollToTop()); + getToolbar().setNavigationContentDescription(R.string.back); + } + + public void scrollToTop(){ + scrollView.smoothScrollTo(0, 0); + } + + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ + inflater.inflate(R.menu.instance_info, menu); + UiUtils.enableOptionsMenuIcons(getActivity(), menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item){ + int id=item.getItemId(); + if (id==R.id.open_timeline) { + Bundle args=new Bundle(); + args.putString("account", accountID); + args.putString("domain", instance.uri); + Nav.go(getActivity(), CustomLocalTimelineFragment.class, args); + }else if (id==R.id.rules) { + Bundle args=new Bundle(); + args.putParcelable("instance", Parcels.wrap(instance)); + Nav.go(getActivity(), InstanceRulesFragment.class, args); + } else if (id==R.id.moderated_servers) { + Bundle args=new Bundle(); + args.putParcelable("instance", Parcels.wrap(instance)); + Nav.go(getActivity(), InstanceBlockListFragment.class, args); + } + return true; + } + + + @Override + public boolean wantsLightStatusBar(){ + return false; + } + @Override + protected int getToolbarResource(){ + return R.layout.profile_toolbar; + } + private void onReadMoreClick(View view) { + isExpanded = !isExpanded; + bindViews(); + } + + + private void onCoverClick(View v){ + Drawable drawable=cover.getDrawable(); + if(drawable==null || drawable instanceof ColorDrawable) + return; + new PhotoViewer(getActivity(), Attachment.createFakeAttachments(instance.thumbnail, drawable), 0, + new SingleImagePhotoViewerListener(cover, cover, null, this, () -> { + }, () -> drawable, null, null)); + } + + public void setFields(ArrayList fields){ + metadataListData=fields; + if (adapter != null) adapter.notifyDataSetChanged(); + } + + private class MetadataAdapter extends UsableRecyclerView.Adapter { + public MetadataAdapter(){ + super(imgLoader); + } + + @NonNull + @Override + public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){ + return new AboutViewHolder(); + } + + @Override + public void onBindViewHolder(BaseViewHolder holder, int position){ + holder.bind(metadataListData.get(position)); + super.onBindViewHolder(holder, position); + } + + @Override + public int getItemCount(){ + return metadataListData.size(); + } + + @Override + public int getItemViewType(int position){ + return 0; + } + + } + + private abstract class BaseViewHolder extends BindableViewHolder { + public BaseViewHolder(int layout){ + super(getActivity(), layout, list); + } + } + + private class AboutViewHolder extends BaseViewHolder { + private final TextView title; + private final LinkedTextView value; + + public AboutViewHolder(){ + super(R.layout.item_profile_about); + title=findViewById(R.id.title); + value=findViewById(R.id.value); + } + + @Override + public void onBind(AccountField item){ + title.setText(item.parsedName); + value.setText(item.parsedValue); + value.setTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary)); + value.setLinkTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.colorAccent)); + value.setCompoundDrawables(null, null, null, null); + } + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java index 0468c5b03..7b3239e90 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java @@ -19,6 +19,7 @@ import android.os.Bundle; import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.text.style.ImageSpan; +import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; @@ -382,6 +383,16 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList followersBtn.setOnClickListener(this::onFollowersOrFollowingClick); followingBtn.setOnClickListener(this::onFollowersOrFollowingClick); + + //this currently takes up the whole username + //in the future it might need to be change to only the instance uri + username.setOnClickListener(v -> { + Bundle args=new Bundle(); + args.putString("account", accountID); + args.putString("instanceDomain", Uri.parse(account.url).getHost()); + Nav.go(getActivity(), InstanceInfoFragment.class, args); + }); + username.setOnLongClickListener(v->{ String usernameString=account.acct; if(!usernameString.contains("@")){ @@ -1132,16 +1143,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList return false; } - private List createFakeAttachments(String url, Drawable drawable){ - Attachment att=new Attachment(); - att.type=Attachment.Type.IMAGE; - att.url=url; - att.meta=new Attachment.Metadata(); - att.meta.width=drawable.getIntrinsicWidth(); - att.meta.height=drawable.getIntrinsicHeight(); - return Collections.singletonList(att); - } - private void onNotifyButtonClick(View v) { UiUtils.performToggleAccountNotifications(getActivity(), account, accountID, relationship, actionButton, this::setNotifyProgressVisible, this::updateRelationship); } @@ -1154,7 +1155,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList if(ava==null) return; int radius=V.dp(25); - currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(account.avatar, ava), 0, + currentPhotoViewer=new PhotoViewer(getActivity(), Attachment.createFakeAttachments(account.avatar, ava), 0, new SingleImagePhotoViewerListener(avatar, avatarBorder, new int[]{radius, radius, radius, radius}, this, ()->currentPhotoViewer=null, ()->ava, null, null)); } } @@ -1166,7 +1167,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList Drawable drawable=cover.getDrawable(); if(drawable==null || drawable instanceof ColorDrawable) return; - currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(account.header, drawable), 0, + currentPhotoViewer=new PhotoViewer(getActivity(), Attachment.createFakeAttachments(account.header, drawable), 0, new SingleImagePhotoViewerListener(cover, cover, null, this, ()->currentPhotoViewer=null, ()->drawable, ()->avatarBorder.setTranslationZ(2), ()->avatarBorder.setTranslationZ(0))); } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/Attachment.java b/mastodon/src/main/java/org/joinmastodon/android/model/Attachment.java index 999e70733..45eea0fd6 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Attachment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Attachment.java @@ -14,6 +14,8 @@ import org.parceler.Parcel; import org.parceler.ParcelConstructor; import org.parceler.ParcelProperty; +import java.util.Collections; +import java.util.List; import java.util.UUID; @Parcel @@ -44,6 +46,16 @@ public class Attachment extends BaseModel{ blurhashPlaceholder=new BlurHashDrawable(placeholder, getWidth(), getHeight()); } } + public static List createFakeAttachments(String url, Drawable drawable){ + Attachment att=new Attachment(); + att.type=Attachment.Type.IMAGE; + att.url=url; + att.meta=new Attachment.Metadata(); + att.meta.width=drawable.getIntrinsicWidth(); + att.meta.height=drawable.getIntrinsicHeight(); + return Collections.singletonList(att); + } + public int getWidth(){ if(meta==null) diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/DomainBlock.java b/mastodon/src/main/java/org/joinmastodon/android/model/DomainBlock.java new file mode 100644 index 000000000..00f974c43 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/model/DomainBlock.java @@ -0,0 +1,27 @@ +package org.joinmastodon.android.model; + +import org.joinmastodon.android.api.RequiredField; +import org.parceler.Parcel; + +@Parcel +public class DomainBlock extends BaseModel { + @RequiredField + public String domain; + @RequiredField + public String digest; + @RequiredField + public Severity severity; + public String comment; + + @Override + public String toString() { + return "DomainBlock{" + + "domain='" + domain + '\'' + + ", digest='" + digest + '\'' + + ", severity='" + severity + '\'' + + ", comment='" + comment + '\'' + + '}'; + } + + +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/ExtendedDescription.java b/mastodon/src/main/java/org/joinmastodon/android/model/ExtendedDescription.java new file mode 100644 index 000000000..c82f4ad6d --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/model/ExtendedDescription.java @@ -0,0 +1,21 @@ +package org.joinmastodon.android.model; + +import org.joinmastodon.android.api.RequiredField; +import org.parceler.Parcel; + +import java.util.List; + +@Parcel +public class ExtendedDescription extends BaseModel{ + @RequiredField + public String content; + public String updatedAt; + + @Override + public String toString() { + return "ExtendedDescription{" + + "content='" + content + '\'' + + ", updatedAt='" + updatedAt + '\'' + + '}'; + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/Severity.java b/mastodon/src/main/java/org/joinmastodon/android/model/Severity.java new file mode 100644 index 000000000..96ffb8d67 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Severity.java @@ -0,0 +1,12 @@ +package org.joinmastodon.android.model; + +import com.google.gson.annotations.SerializedName; + +import org.parceler.Parcel; + +public enum Severity { + @SerializedName("silence") + SILENCE, + @SerializedName("suspend") + SUSPEND +} \ No newline at end of file diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/WeeklyActivity.java b/mastodon/src/main/java/org/joinmastodon/android/model/WeeklyActivity.java new file mode 100644 index 000000000..58e794acc --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/model/WeeklyActivity.java @@ -0,0 +1,26 @@ +package org.joinmastodon.android.model; + +import org.joinmastodon.android.api.RequiredField; +import org.parceler.Parcel; + +@Parcel +public class WeeklyActivity extends BaseModel { + @RequiredField + public String week; + @RequiredField + public int statuses; + @RequiredField + public int logins; + @RequiredField + public int registrations; + + @Override + public String toString() { + return "WeeklyActivity{" + + "week=" + week + + ", statuses=" + statuses + + ", logins=" + logins + + ", registrations=" + registrations + + '}'; + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java index c20d7dc2a..0459e269e 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java @@ -920,6 +920,8 @@ public class UiUtils { } } + /// Add icons to the menu. + /// Passing in items will be colored to be visible on the background. public static void enableOptionsMenuIcons(Context context, Menu menu, @IdRes int... asAction) { if(menu.getClass().getSimpleName().equals("MenuBuilder")){ try { diff --git a/mastodon/src/main/res/drawable/ic_fluent_book_exclamation_mark_24_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_book_exclamation_mark_24_regular.xml new file mode 100644 index 000000000..9c86090e1 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_book_exclamation_mark_24_regular.xml @@ -0,0 +1,9 @@ + + + diff --git a/mastodon/src/main/res/drawable/ic_fluent_shield_prohibited_28_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_shield_prohibited_28_regular.xml new file mode 100644 index 000000000..3c8aaab1e --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_shield_prohibited_28_regular.xml @@ -0,0 +1,3 @@ + + + diff --git a/mastodon/src/main/res/layout/fragment_instance_info.xml b/mastodon/src/main/res/layout/fragment_instance_info.xml new file mode 100644 index 000000000..b0ede11c5 --- /dev/null +++ b/mastodon/src/main/res/layout/fragment_instance_info.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + +