Compare commits
103 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
162bc86ebe | ||
|
|
af8f042f10 | ||
|
|
7aae8b03af | ||
|
|
b28c095226 | ||
|
|
9432fc9b8c | ||
|
|
37df47c7cd | ||
|
|
62602839db | ||
|
|
52c56db1ce | ||
|
|
cdc3b37ee4 | ||
|
|
46bd36b65d | ||
|
|
70eb5bf68c | ||
|
|
c2f6b16aff | ||
|
|
603c058ec9 | ||
|
|
3582d7bdad | ||
|
|
d988e1aecf | ||
|
|
7bf322d48a | ||
|
|
5c4a450ef0 | ||
|
|
c370fab1b4 | ||
|
|
c79cba96ec | ||
|
|
ca4aed3dc2 | ||
|
|
c20237d32c | ||
|
|
6082a0bcd8 | ||
|
|
3de494f9e9 | ||
|
|
1b6c299251 | ||
|
|
01ae5b915d | ||
|
|
d0ca465194 | ||
|
|
07564f2964 | ||
|
|
eb45b59cac | ||
|
|
6e4c4c86f6 | ||
|
|
b52dd603a1 | ||
|
|
573e13f39f | ||
|
|
5848dc0e67 | ||
|
|
7ae5546113 | ||
|
|
a126a078b4 | ||
|
|
808dab6f50 | ||
|
|
70dc5aece0 | ||
|
|
80f76d0f05 | ||
|
|
d2a96af886 | ||
|
|
6b6e720ca5 | ||
|
|
9eacb7b067 | ||
|
|
36cce87ffc | ||
|
|
b028c3ad38 | ||
|
|
eada060f57 | ||
|
|
53cfbcb5b0 | ||
|
|
23c624f575 | ||
|
|
669f3a50c8 | ||
|
|
fa3f4f6eda | ||
|
|
d97ffc32aa | ||
|
|
1aecf4021f | ||
|
|
f3d76a26f7 | ||
|
|
e5dc62db6f | ||
|
|
6008368045 | ||
|
|
83af61a758 | ||
|
|
eefcc34277 | ||
|
|
4a65976eea | ||
|
|
cb8aea258a | ||
|
|
e5297b023d | ||
|
|
21c6f41013 | ||
|
|
19f8d908c7 | ||
|
|
fae7f73f7a | ||
|
|
41da8cc1d7 | ||
|
|
39d5a32494 | ||
|
|
c885b5a85e | ||
|
|
76f2b63171 | ||
|
|
58e35d8da3 | ||
|
|
a5e03357df | ||
|
|
5a7ab6be70 | ||
|
|
e977b46392 | ||
|
|
66de2f4b87 | ||
|
|
1a48277cf2 | ||
|
|
6a55bd2248 | ||
|
|
3a82395428 | ||
|
|
a98b93feec | ||
|
|
0cff2658f3 | ||
|
|
d3d95d49de | ||
|
|
71ca9b6f3d | ||
|
|
bf9c3d4d8d | ||
|
|
4b304629b7 | ||
|
|
c4314f100e | ||
|
|
14c250446c | ||
|
|
8bc1c8e79c | ||
|
|
5e6781817d | ||
|
|
5b5e4fbbd2 | ||
|
|
3dcc6d0013 | ||
|
|
2ad50cd972 | ||
|
|
ced5fe4ee0 | ||
|
|
db4afd4c8f | ||
|
|
a0d3bd83f2 | ||
|
|
2092a6b8fe | ||
|
|
ad04433944 | ||
|
|
57da77b642 | ||
|
|
f80e3771d1 | ||
|
|
6a18d8ef03 | ||
|
|
a312018441 | ||
|
|
3026bd5c51 | ||
|
|
5afde48052 | ||
|
|
14209dc785 | ||
|
|
f1b30f251c | ||
|
|
6a849d654f | ||
|
|
2f9c5fe210 | ||
|
|
43f096420f | ||
|
|
5b848ed1ca | ||
|
|
9e1cf330d7 |
7
fastlane/metadata/android/en-US/changelogs/125.txt
Normal file
7
fastlane/metadata/android/en-US/changelogs/125.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
- New option to use our color theme instead of Material You
|
||||
- Support for the color contrast setting in Android 15
|
||||
- You can now crop your profile picture when editing your profile
|
||||
- You can now long-press "Add media" in the composer to bring up the file picker
|
||||
- New look for the media viewer
|
||||
- New follower notifications will now appear grouped
|
||||
- Directional loading for gaps in timelines: load newer posts when scrolling up, older when down
|
||||
@@ -1 +1 @@
|
||||
116.txt
|
||||
125.txt
|
||||
@@ -1,36 +1,36 @@
|
||||
Is Mastodon an dòigh as fheàrr airson sùil a chumail air na tha a’ dol. Lean duine sam bith air a’ cho-shaoghal agus faic a h-uile càil a-rèir an ama. Chan eil sgeul air algairimean, sanasachd no clickbait.
|
||||
|
||||
Seo an aplacaid Android oifigeil airson Mastodon. It is blazing fast and stunningly beautiful, designed to be not just powerful but also easy to use. In our app, you can:
|
||||
Seo an aplacaid Android oifigeil airson Mastodon. The e àlainn ’s cho luath ris a’ ghaoth, air a dhealbhadh ach am biodh e cumhachdach ach furasta cleachdadh. Seo na nì thu san aplacaid againn:
|
||||
|
||||
EXPLORE
|
||||
RÙRAICH
|
||||
|
||||
■ Discover new writers, journalists, artists, photographers, scientists and more
|
||||
■ See what’s happening in the world
|
||||
■ Lorg sgrìobhadairean, luchd-naidheachd, luchd-ealain, luchd togail dhealbhan, luchd-saidheans is eile
|
||||
■ Faic na tha a’ dol air an t-saoghal
|
||||
|
||||
READ
|
||||
LEUGH
|
||||
|
||||
■ Keep up with people you care about in a chronological feed with no interruptions
|
||||
■ Follow hashtags to keep up with specific topics in real time
|
||||
■ Gabh naidheachdan na feadhainn a tha cudromach dhut a-rèir an ama ’s gun bhuairidhean
|
||||
■ Lean tagaichean hais airson ceum a chumail ri cuspairean àraid ann am fìor-àm
|
||||
|
||||
CREATE
|
||||
CRUTHAICH
|
||||
|
||||
■ Post to your followers or the whole world, with polls, high quality images and videos
|
||||
■ Participate in interesting conversations with other people
|
||||
■ Postaich chun luchd-leantainn agad no chun t-saoghail air fad, le cunntasan-bheachd agus dealbhan ’s videothan le càileachd àrd
|
||||
■ Gabh pàirt ann an còmhraidhean inntinneach le daoine eile
|
||||
|
||||
CURATE
|
||||
CURAIDICH
|
||||
|
||||
■ Create lists of people to never miss a post
|
||||
■ Filter words or phrases to control what you do and don’t want to see
|
||||
■ Cruthaich liostaichean de dhaoine ach nach caill thu post uapa-san
|
||||
■ Criathraich faclan no abairtean a stiùireadh na chì ’s nach fhaic thu
|
||||
|
||||
AND MORE!
|
||||
AGUS MÒRAN A BHARRACHD!
|
||||
|
||||
■ A beautiful theme that adapts to your personalized color scheme, light or dark
|
||||
■ Share and scan QR codes to quickly exchange Mastodon profiles with others
|
||||
■ Login and switch between multiple accounts
|
||||
■ Get notified when a specific person posts with the bell button
|
||||
■ No spoilers! You can put your posts behind content warnings
|
||||
■ Ùrlar àlainn a fhreagras dhan sgeama dhathan phearsanaichte agad, soilleir no dorcha
|
||||
■ Co-roinn is sganaich còdaichean QR airson pròifilean Mhastodon iomlaid le càch sa bhad
|
||||
■ Clàraich a-steach ’s geàrr leum eadar iomadh cunntas
|
||||
■ Faigh brath nuair a phostaich cuideigin sònraichte rud le putan a’ chluig
|
||||
■ Gun spoilers! ’S urrainn dhut na postaichean agad a chur air cùlaibh rabhaidhean susbainte
|
||||
|
||||
A POWERFUL PUBLISHING PLATFORM
|
||||
ÙRLAR FOILLSEACHAIDH CUMHACHDACH
|
||||
|
||||
You no longer have to try and appease an opaque algorithm that decides if your friends are going to see what you posted. If they follow you, they’ll see it.
|
||||
|
||||
|
||||
@@ -34,13 +34,13 @@ OG FLEIRA!
|
||||
|
||||
Þú þarft ekki lengur að prófa þig áfram með og friðþægja eitthvert ógagnsætt algrími sem ákvarðar hvort vinir þínir fái að sjá það sem þú birtir. Ef viðkomandi fylgist með þér, mun það sjást.
|
||||
|
||||
Ef þú birtir það á opna vefnum, er hægt að skoða það á opna vefnum. You can safely share links to Mastodon in the knowledge that anyone will be able to read them without logging in.
|
||||
Ef þú birtir það á opna vefnum, er hægt að skoða það á opna vefnum. Þú ert örugg(ur) við að deila tenglum á Mastodon, vitandi það að hver sem er mun geta lesið þá án þess að skrá sig inn.
|
||||
|
||||
Between threads, polls, high quality images, videos, audio, and content warnings, Mastodon offers plenty of ways to express yourself in a way that suits you.
|
||||
Með samræðum, hágæða myndefni, myndskeiðum, hljóðskrám og viðvörunum vegna efnis, býður Mastodon upp á margar leiðir til að tjá þig á þann hátt sem þér hentar.
|
||||
|
||||
ÖFLUGT KERFI TIL LESTRAR
|
||||
|
||||
We don’t need to show you ads, so we don’t need to keep you in our app. Mastodon has the richest selection of 3rd party apps and integrations so you can choose the experience that fits you best.
|
||||
Við þurfum ekkert að sýna þér auglýsingar og höfum því enga ástæðu til að halda þér inni í okkar eigin forritum. Mastodon býður upp á mikið úrval forrita frá utanaðkomandi aðilum og samþættingu við önnur kerfi, þannig að þú getir valið það sem þér líkar best.
|
||||
|
||||
Thanks to the chronological home feed, it’s easy to tell when you’ve caught up on all updates and can move on to something else.
|
||||
|
||||
@@ -58,4 +58,4 @@ Ekki ánægð/ur með valið þitt? Þú getur alltaf skipt yfir á annan Mastod
|
||||
|
||||
Mastodon er skráð sem samtök án hagnaðarmarkmiða í BNA og Þýskalandi. We are not motivated by extracting monetary value from the platform, but by what’s best for the platform.
|
||||
|
||||
AS FEATURED IN: TIME, Forbes, Wired, The Guardian, CNN, The Verge, TechCrunch, Financial Times, Gizmodo, PCMAG.com, and more.
|
||||
EINS OG BIRST HEFUR Í: TIME, Forbes, Wired, The Guardian, CNN, The Verge, TechCrunch, Financial Times, Gizmodo, PCMAG.com og víðar.
|
||||
@@ -13,8 +13,8 @@ android {
|
||||
applicationId "org.joinmastodon.android"
|
||||
minSdk 23
|
||||
targetSdk 34
|
||||
versionCode 123
|
||||
versionName "2.7.3"
|
||||
versionCode 125
|
||||
versionName "2.8.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ dependencies {
|
||||
implementation 'me.grishka.litex:viewpager:1.0.0'
|
||||
implementation 'me.grishka.litex:viewpager2:1.0.0'
|
||||
implementation 'me.grishka.litex:palette:1.0.0'
|
||||
implementation 'me.grishka.appkit:appkit:1.4.3'
|
||||
implementation 'me.grishka.appkit:appkit:1.4.4'
|
||||
implementation 'com.google.code.gson:gson:2.8.9'
|
||||
implementation 'org.jsoup:jsoup:1.14.3'
|
||||
implementation 'com.squareup:otto:1.3.8'
|
||||
|
||||
@@ -11,6 +11,7 @@ public class GlobalUserPreferences{
|
||||
public static boolean useCustomTabs;
|
||||
public static boolean altTextReminders, confirmUnfollow, confirmBoost, confirmDeletePost;
|
||||
public static ThemePreference theme=ThemePreference.AUTO;
|
||||
public static boolean useDynamicColors;
|
||||
|
||||
private static SharedPreferences getPrefs(){
|
||||
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
||||
@@ -29,6 +30,7 @@ public class GlobalUserPreferences{
|
||||
confirmBoost=prefs.getBoolean("confirmBoost", false);
|
||||
confirmDeletePost=prefs.getBoolean("confirmDeletePost", true);
|
||||
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
|
||||
useDynamicColors=prefs.getBoolean("useDynamicColors", true);
|
||||
}
|
||||
|
||||
public static void save(){
|
||||
@@ -40,6 +42,7 @@ public class GlobalUserPreferences{
|
||||
.putBoolean("confirmUnfollow", confirmUnfollow)
|
||||
.putBoolean("confirmBoost", confirmBoost)
|
||||
.putBoolean("confirmDeletePost", confirmDeletePost)
|
||||
.putBoolean("useDynamicColors", useDynamicColors)
|
||||
.apply();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.joinmastodon.android;
|
||||
import android.Manifest;
|
||||
import android.app.Application;
|
||||
import android.app.Fragment;
|
||||
import android.app.assist.AssistContent;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
@@ -16,6 +17,7 @@ import org.joinmastodon.android.api.ObjectValidationException;
|
||||
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.AssistContentProviderFragment;
|
||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||
import org.joinmastodon.android.fragments.HomeFragment;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
@@ -229,4 +231,11 @@ public class MainActivity extends FragmentStackActivity{
|
||||
return null;
|
||||
return getFragmentManager().findFragmentById(fragmentContainers.get(fragmentContainers.size()-1).getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvideAssistContent(AssistContent outContent){
|
||||
if(getTopmostFragment() instanceof AssistContentProviderFragment provider){
|
||||
provider.onProvideAssistContent(outContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import okio.Source;
|
||||
public class ResizedImageRequestBody extends CountingRequestBody{
|
||||
private File tempFile;
|
||||
private Uri uri;
|
||||
private String contentType;
|
||||
private MediaType contentType;
|
||||
private int maxSize;
|
||||
|
||||
public ResizedImageRequestBody(Uri uri, int maxSize, ProgressListener progressListener) throws IOException{
|
||||
@@ -42,15 +42,16 @@ public class ResizedImageRequestBody extends CountingRequestBody{
|
||||
opts.inJustDecodeBounds=true;
|
||||
if("file".equals(uri.getScheme())){
|
||||
BitmapFactory.decodeFile(uri.getPath(), opts);
|
||||
contentType=UiUtils.getFileMediaType(new File(uri.getPath())).type();
|
||||
contentType=UiUtils.getFileMediaType(new File(uri.getPath()));
|
||||
}else{
|
||||
try(InputStream in=MastodonApp.context.getContentResolver().openInputStream(uri)){
|
||||
BitmapFactory.decodeStream(in, null, opts);
|
||||
}
|
||||
contentType=MastodonApp.context.getContentResolver().getType(uri);
|
||||
String mime=MastodonApp.context.getContentResolver().getType(uri);
|
||||
contentType=TextUtils.isEmpty(mime) ? null : MediaType.get(mime);
|
||||
}
|
||||
if(TextUtils.isEmpty(contentType))
|
||||
contentType="image/jpeg";
|
||||
if(contentType==null)
|
||||
contentType=MediaType.get("image/jpeg");
|
||||
if(needResize(opts.outWidth, opts.outHeight) || needCrop(opts.outWidth, opts.outHeight)){
|
||||
Bitmap bitmap;
|
||||
if(Build.VERSION.SDK_INT>=28){
|
||||
@@ -136,7 +137,7 @@ public class ResizedImageRequestBody extends CountingRequestBody{
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 0, out);
|
||||
}else{
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 97, out);
|
||||
contentType="image/jpeg";
|
||||
contentType=MediaType.get("image/jpeg");
|
||||
}
|
||||
}
|
||||
length=tempFile.length();
|
||||
@@ -163,7 +164,7 @@ public class ResizedImageRequestBody extends CountingRequestBody{
|
||||
|
||||
@Override
|
||||
public MediaType contentType(){
|
||||
return MediaType.get(contentType);
|
||||
return contentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -124,6 +124,7 @@ public class AccountSessionManager{
|
||||
|
||||
public void addAccount(Instance instance, Token token, Account self, Application app, AccountActivationInfo activationInfo){
|
||||
instances.put(instance.getDomain(), instance);
|
||||
runOnDbThread(db->insertInstanceIntoDatabase(db, instance.getDomain(), instance, List.of(), 0));
|
||||
AccountSession session=new AccountSession(token, self, app, instance.getDomain(), activationInfo==null, activationInfo);
|
||||
sessions.put(session.getID(), session);
|
||||
lastActiveAccountID=session.getID();
|
||||
@@ -349,6 +350,7 @@ public class AccountSessionManager{
|
||||
@Override
|
||||
public void onSuccess(Instance instance){
|
||||
instances.put(domain, instance);
|
||||
runOnDbThread(db->insertInstanceIntoDatabase(db, domain, instance, List.of(), 0));
|
||||
updateInstanceEmojis(instance, domain);
|
||||
}
|
||||
|
||||
@@ -581,6 +583,12 @@ public class AccountSessionManager{
|
||||
runOnDbThread(db->db.delete("dismissed_donation_campaigns", null, null));
|
||||
}
|
||||
|
||||
public void clearInstanceInfo(){
|
||||
SQLiteDatabase db=getOrOpenDatabase();
|
||||
db.delete("instances", null, null);
|
||||
db.close();
|
||||
}
|
||||
|
||||
private static void insertInstanceIntoDatabase(SQLiteDatabase db, String domain, Instance instance, List<Emoji> emojis, long lastUpdated){
|
||||
ContentValues values=new ContentValues();
|
||||
values.put("domain", domain);
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.assist.AssistContent;
|
||||
|
||||
public interface AssistContentProviderFragment{
|
||||
void onProvideAssistContent(AssistContent content);
|
||||
}
|
||||
@@ -186,7 +186,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
@Override
|
||||
public void openPhotoViewer(String parentID, Status _status, int attachmentIndex, MediaGridStatusDisplayItem.Holder gridHolder){
|
||||
final Status status=_status.getContentStatus();
|
||||
currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, status, accountID, new PhotoViewer.Listener(){
|
||||
currentPhotoViewer=new PhotoViewer(getActivity(), this, status.mediaAttachments, attachmentIndex, status, accountID, new PhotoViewer.Listener(){
|
||||
private MediaAttachmentViewController transitioningHolder;
|
||||
|
||||
@Override
|
||||
|
||||
@@ -290,7 +290,13 @@ public class ComposeFragment extends MastodonToolbarFragment implements ComposeE
|
||||
languageBtn=view.findViewById(R.id.btn_language);
|
||||
replyText=view.findViewById(R.id.reply_text);
|
||||
|
||||
mediaBtn.setOnClickListener(v->openFilePicker());
|
||||
mediaBtn.setOnClickListener(v->openFilePicker(false));
|
||||
if(UiUtils.isPhotoPickerAvailable()){
|
||||
mediaBtn.setOnLongClickListener(v->{
|
||||
openFilePicker(true);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
pollBtn.setOnClickListener(v->togglePoll());
|
||||
emojiBtn.setOnClickListener(v->emojiKeyboard.toggleKeyboardPopup(mainEditText));
|
||||
spoilerBtn.setOnClickListener(v->toggleSpoiler());
|
||||
@@ -887,9 +893,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements ComposeE
|
||||
*
|
||||
* <p>For earlier versions use the built in docs ui via {@link Intent#ACTION_GET_CONTENT}
|
||||
*/
|
||||
private void openFilePicker(){
|
||||
private void openFilePicker(boolean forceGetContent){
|
||||
Intent intent;
|
||||
boolean usePhotoPicker=UiUtils.isPhotoPickerAvailable();
|
||||
boolean usePhotoPicker=!forceGetContent && UiUtils.isPhotoPickerAvailable();
|
||||
if(usePhotoPicker){
|
||||
intent=new Intent(MediaStore.ACTION_PICK_IMAGES);
|
||||
if(mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount()>1)
|
||||
|
||||
@@ -166,7 +166,7 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment{
|
||||
fakeAttachment.meta.width=width;
|
||||
fakeAttachment.meta.height=height;
|
||||
|
||||
photoViewer=new PhotoViewer(getActivity(), Collections.singletonList(fakeAttachment), 0, null, accountID, new PhotoViewer.Listener(){
|
||||
photoViewer=new PhotoViewer(getActivity(), null, Collections.singletonList(fakeAttachment), 0, null, accountID, new PhotoViewer.Listener(){
|
||||
@Override
|
||||
public void setPhotoViewVisibility(int index, boolean visible){
|
||||
image.setAlpha(visible ? 1f : 0f);
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.joinmastodon.android.fragments;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Fragment;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.assist.AssistContent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -57,7 +58,7 @@ import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
|
||||
public class HomeFragment extends AppKitFragment{
|
||||
public class HomeFragment extends AppKitFragment implements AssistContentProviderFragment{
|
||||
private FragmentRootLinearLayout content;
|
||||
private HomeTimelineFragment homeTimelineFragment;
|
||||
private NotificationsListFragment notificationsFragment;
|
||||
@@ -379,4 +380,11 @@ public class HomeFragment extends AppKitFragment{
|
||||
if(notificationsFragment.loaded)
|
||||
notificationsFragment.rebuildAllDisplayItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvideAssistContent(AssistContent content){
|
||||
if(fragmentForTab(currentTab) instanceof AssistContentProviderFragment provider){
|
||||
provider.onProvideAssistContent(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.VibrationEffect;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
@@ -31,10 +33,12 @@ import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
@@ -58,20 +62,25 @@ import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.sheets.DonationSheet;
|
||||
import org.joinmastodon.android.ui.sheets.DonationSuccessfulSheet;
|
||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.viewcontrollers.HomeTimelineMenuController;
|
||||
import org.joinmastodon.android.ui.viewcontrollers.ToolbarDropdownMenuController;
|
||||
import org.joinmastodon.android.ui.views.FixedAspectRatioImageView;
|
||||
import org.joinmastodon.android.ui.views.NestedRecyclerScrollView;
|
||||
import org.joinmastodon.android.ui.views.NewPostsButtonContainer;
|
||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
@@ -102,6 +111,11 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
||||
private DiscoverInfoBannerHelper localTimelineBannerHelper;
|
||||
private View donationBanner;
|
||||
private boolean donationBannerDismissing;
|
||||
private NestedRecyclerScrollView scrollWrapper;
|
||||
|
||||
private String scrollBackItemID;
|
||||
private int scrollBackItemOffset, scrollBackItemIndex;
|
||||
private long scrollBackTime;
|
||||
|
||||
private String maxID;
|
||||
private String lastSavedMarkerID;
|
||||
@@ -109,6 +123,7 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
||||
private BottomSheet donationSheet;
|
||||
|
||||
public HomeTimelineFragment(){
|
||||
setLayout(R.layout.fragment_loader_hiding_toolbar);
|
||||
setListLayoutId(R.layout.fragment_timeline);
|
||||
}
|
||||
|
||||
@@ -272,13 +287,53 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
||||
newPostsBtnWrap.setOnHideButtonListener(this::hideNewPostsButton);
|
||||
updateToolbarLogo();
|
||||
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
|
||||
private HashSet<GapStatusDisplayItem> gaps=new HashSet<>();
|
||||
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
|
||||
if(newPostsBtnShown && list.getChildAdapterPosition(list.getChildAt(0))<=getMainAdapterOffset()){
|
||||
hideNewPostsButton();
|
||||
}
|
||||
for(StatusDisplayItem item:displayItems){
|
||||
if(item instanceof GapStatusDisplayItem gap){
|
||||
gaps.add(gap);
|
||||
}
|
||||
}
|
||||
if(gaps.isEmpty())
|
||||
return;
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
View child=list.getChildAt(i);
|
||||
if(list.getChildViewHolder(child) instanceof GapStatusDisplayItem.Holder holder){
|
||||
GapStatusDisplayItem gap=holder.getItem();
|
||||
if(!gap.visible){
|
||||
gap.visible=true;
|
||||
gap.enteredFromTop=child.getTop()<list.getHeight()/2;
|
||||
gaps.remove(gap);
|
||||
}
|
||||
}
|
||||
}
|
||||
for(GapStatusDisplayItem gap:gaps){
|
||||
gap.visible=false;
|
||||
}
|
||||
gaps.clear();
|
||||
}
|
||||
});
|
||||
View bottomOverlays=view.findViewById(R.id.bottom_overlays);
|
||||
NestedRecyclerScrollView scroller=view.findViewById(R.id.scroller);
|
||||
scroller.setScrollableChildSupplier(()->list);
|
||||
scroller.setTakePriorityOverChildViews(true);
|
||||
scroller.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY)->{
|
||||
bottomOverlays.setTranslationY(scrollY-getToolbar().getHeight());
|
||||
});
|
||||
scroller.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||
@Override
|
||||
public boolean onPreDraw(){
|
||||
scroller.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
bottomOverlays.setTranslationY(scroller.getScrollY()-getToolbar().getHeight());
|
||||
return true;
|
||||
}
|
||||
});
|
||||
scrollWrapper=scroller;
|
||||
|
||||
if(GithubSelfUpdater.needSelfUpdating()){
|
||||
updateUpdateState(GithubSelfUpdater.getInstance().getState());
|
||||
@@ -297,6 +352,10 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
||||
state=updater.getState();
|
||||
if(state!=GithubSelfUpdater.UpdateState.NO_UPDATE && state!=GithubSelfUpdater.UpdateState.CHECKING)
|
||||
getToolbar().getMenu().findItem(R.id.settings).setIcon(R.drawable.ic_settings_updateready_24px);
|
||||
|
||||
if("debug".equals(BuildConfig.BUILD_TYPE)){
|
||||
menu.add(0, 1, 0, "Make a gap");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -309,6 +368,17 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
||||
}else if(id==R.id.edit_list){
|
||||
args.putParcelable("list", Parcels.wrap(currentList));
|
||||
Nav.go(getActivity(), EditListFragment.class, args);
|
||||
}else if(id==1){
|
||||
if(data.size()<35){
|
||||
Toast.makeText(getActivity(), "Too few posts. Load at least 35", Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
}
|
||||
Status gapStatus=data.get(1);
|
||||
gapStatus.hasGapAfter=true;
|
||||
onStatusUpdated(gapStatus);
|
||||
for(Status s:new ArrayList<>(data.subList(2, 32))){
|
||||
removeStatus(s);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -386,6 +456,10 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
||||
result.get(result.size()-1).hasGapAfter=true;
|
||||
toAdd=result;
|
||||
}
|
||||
if(!(toAdd instanceof ArrayList<?>))
|
||||
toAdd=new ArrayList<>(toAdd);
|
||||
Set<String> existingPostIDs=data.stream().map(s->s.id).collect(Collectors.toSet());
|
||||
toAdd.removeIf(s->existingPostIDs.contains(s.id));
|
||||
if(needCache)
|
||||
AccountSessionManager.get(accountID).filterStatuses(toAdd, FilterContext.HOME);
|
||||
if(!toAdd.isEmpty()){
|
||||
@@ -408,13 +482,23 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
||||
public void onGapClick(GapStatusDisplayItem.Holder item){
|
||||
if(dataLoading)
|
||||
return;
|
||||
item.getItem().loading=true;
|
||||
GapStatusDisplayItem gap=item.getItem();
|
||||
gap.loading=true;
|
||||
V.setVisibilityAnimated(item.progress, View.VISIBLE);
|
||||
V.setVisibilityAnimated(item.text, View.GONE);
|
||||
GapStatusDisplayItem gap=item.getItem();
|
||||
dataLoading=true;
|
||||
boolean needCache=listMode==ListMode.FOLLOWING;
|
||||
loadAdditionalPosts(item.getItemID(), null, 20, null, new Callback<>(){
|
||||
boolean insertBelowGap=!gap.enteredFromTop;
|
||||
String maxID, minID;
|
||||
if(gap.enteredFromTop){
|
||||
maxID=null;
|
||||
int gapPos=displayItems.indexOf(gap);
|
||||
minID=displayItems.get(gapPos+1).parentID;
|
||||
}else{
|
||||
maxID=item.getItemID();
|
||||
minID=null;
|
||||
}
|
||||
loadAdditionalPosts(maxID, minID, 20, null, new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
|
||||
@@ -432,9 +516,9 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
||||
if(gapStatus!=null){
|
||||
gapStatus.hasGapAfter=false;
|
||||
if(needCache)
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false);
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(List.of(gapStatus), false);
|
||||
}
|
||||
}else{
|
||||
}else if(insertBelowGap){
|
||||
Set<String> idsBelowGap=new HashSet<>();
|
||||
boolean belowGap=false;
|
||||
int gapPostIndex=0;
|
||||
@@ -445,7 +529,7 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
||||
belowGap=true;
|
||||
s.hasGapAfter=false;
|
||||
if(needCache)
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false);
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(List.of(s), false);
|
||||
}else{
|
||||
gapPostIndex++;
|
||||
}
|
||||
@@ -463,8 +547,8 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
||||
}
|
||||
if(needCache)
|
||||
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.HOME);
|
||||
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
||||
targetList.clear();
|
||||
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1); // Get a sub-list that contains the gap item
|
||||
targetList.clear(); // remove the gap item
|
||||
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
||||
for(Status s:result){
|
||||
if(idsBelowGap.contains(s.id))
|
||||
@@ -481,6 +565,61 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
||||
}
|
||||
if(needCache)
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
|
||||
}else{
|
||||
Set<String> idsAboveGap=new HashSet<>();
|
||||
int gapPostIndex=0;
|
||||
Status gapPost=null;
|
||||
for(Status s:data){
|
||||
if(s.id.equals(gap.parentID)){
|
||||
gapPost=s;
|
||||
break;
|
||||
}else{
|
||||
idsAboveGap.add(s.id);
|
||||
gapPostIndex++;
|
||||
}
|
||||
}
|
||||
if(gapPost==null)
|
||||
return;
|
||||
boolean needAdjustScroll=false;
|
||||
int scrollTop=0;
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
View child=list.getChildAt(i);
|
||||
if(list.getChildViewHolder(child) instanceof GapStatusDisplayItem.Holder gapHolder && gapHolder.getItem()==gap){
|
||||
needAdjustScroll=true;
|
||||
scrollTop=child.getBottom()+1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
List<StatusDisplayItem> targetList=displayItems.subList(gapPos+1, gapPos+1);
|
||||
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
||||
for(int i=result.size()-1;i>=0;i--){
|
||||
Status s=result.get(i);
|
||||
if(idsAboveGap.contains(s.id))
|
||||
break;
|
||||
targetList.addAll(0, buildDisplayItems(s));
|
||||
insertedPosts.add(0, s);
|
||||
}
|
||||
int addedItemCount=targetList.size();
|
||||
boolean gapRemoved=false;
|
||||
if(insertedPosts.size()<result.size()){ // There was an intersection, remove the gap
|
||||
gapRemoved=true;
|
||||
gapPost.hasGapAfter=false;
|
||||
if(needCache)
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(List.of(gapPost), false);
|
||||
displayItems.remove(gapPos);
|
||||
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
|
||||
}else{
|
||||
gap.loading=false;
|
||||
adapter.notifyItemChanged(getMainAdapterOffset()+gapPos);
|
||||
}
|
||||
if(!insertedPosts.isEmpty()){
|
||||
if(needCache)
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
|
||||
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+(gapRemoved ? 0 : 1), addedItemCount);
|
||||
if(needAdjustScroll){
|
||||
((LinearLayoutManager)list.getLayoutManager()).scrollToPositionWithOffset(getMainAdapterOffset()+gapPos+(gapRemoved ? 0 : 1)+addedItemCount, scrollTop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,7 +755,7 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
||||
private void onNewPostsBtnClick(View v){
|
||||
if(newPostsBtnShown){
|
||||
hideNewPostsButton();
|
||||
scrollToTop();
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -723,6 +862,68 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollToTop(){
|
||||
if(list.getChildCount()==0)
|
||||
return;
|
||||
scrollWrapper.smoothScrollTo(0, 0);
|
||||
View topChild=list.getLayoutManager().getChildAt(0);
|
||||
if(list.getChildAdapterPosition(topChild)==0){
|
||||
if(topChild.getTop()==list.getPaddingTop() && scrollBackItemID!=null && System.currentTimeMillis()-scrollBackTime<5*60_000){
|
||||
int indexWithinPost=0;
|
||||
for(int i=0;i<displayItems.size();i++){
|
||||
StatusDisplayItem item=displayItems.get(i);
|
||||
if(item.parentID.equals(scrollBackItemID)){
|
||||
if(indexWithinPost==scrollBackItemIndex){
|
||||
((LinearLayoutManager)list.getLayoutManager()).scrollToPositionWithOffset(i+getMainAdapterOffset(), scrollBackItemOffset);
|
||||
list.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||
@Override
|
||||
public boolean onPreDraw(){
|
||||
list.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
list.scrollBy(0, V.dp(-300));
|
||||
list.smoothScrollBy(0, V.dp(300));
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.S)
|
||||
UiUtils.playVibrationEffectIfSupported(getActivity(), VibrationEffect.Composition.PRIMITIVE_THUD);
|
||||
return;
|
||||
}
|
||||
indexWithinPost++;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
return;
|
||||
}
|
||||
}else if(list.getChildViewHolder(topChild) instanceof StatusDisplayItem.Holder<?> itemHolder){
|
||||
int postIndex;
|
||||
String id=itemHolder.getItemID();
|
||||
for(postIndex=0;postIndex<data.size();postIndex++){
|
||||
if(data.get(postIndex).id.equals(id))
|
||||
break;
|
||||
}
|
||||
if(postIndex>1){
|
||||
scrollBackItemID=id;
|
||||
scrollBackItemIndex=0;
|
||||
for(StatusDisplayItem item:displayItems){
|
||||
if(item.parentID.equals(id)){
|
||||
if(item==itemHolder.getItem())
|
||||
break;
|
||||
scrollBackItemIndex++;
|
||||
}
|
||||
}
|
||||
scrollBackItemOffset=topChild.getTop();
|
||||
scrollBackTime=System.currentTimeMillis();
|
||||
}else{
|
||||
scrollBackItemID=null;
|
||||
}
|
||||
}
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.S)
|
||||
UiUtils.playVibrationEffectIfSupported(getActivity(), VibrationEffect.Composition.PRIMITIVE_QUICK_RISE);
|
||||
}
|
||||
|
||||
private String getCurrentListTitle(){
|
||||
return switch(listMode){
|
||||
case FOLLOWING -> getString(R.string.timeline_following);
|
||||
|
||||
@@ -281,7 +281,7 @@ public class ListMembersFragment extends PaginatedAccountListFragment implements
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item){
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.confirm_remove_list_members)
|
||||
.setTitle(selectedAccounts.size()>1 ? R.string.confirm_remove_list_members : R.string.confirm_remove_list_member)
|
||||
.setPositiveButton(R.string.remove, (dlg, which)->removeAccounts(new HashSet<>(selectedAccounts), null))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
|
||||
@@ -6,9 +6,11 @@ import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.app.assist.AssistContent;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Outline;
|
||||
@@ -66,6 +68,7 @@ import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||
import org.joinmastodon.android.ui.SingleImagePhotoViewerListener;
|
||||
import org.joinmastodon.android.ui.Snackbar;
|
||||
import org.joinmastodon.android.ui.photoviewer.AvatarCropper;
|
||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
||||
import org.joinmastodon.android.ui.sheets.DecentralizationExplainerSheet;
|
||||
import org.joinmastodon.android.ui.tabs.TabLayout;
|
||||
@@ -106,7 +109,7 @@ import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
|
||||
public class ProfileFragment extends LoaderFragment implements ScrollableToTop{
|
||||
public class ProfileFragment extends LoaderFragment implements ScrollableToTop, AssistContentProviderFragment{
|
||||
private static final int AVATAR_RESULT=722;
|
||||
private static final int COVER_RESULT=343;
|
||||
|
||||
@@ -554,8 +557,8 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop{
|
||||
}
|
||||
setTitle(account.displayName);
|
||||
setSubtitle(getResources().getQuantityString(R.plurals.x_posts, (int)(account.statusesCount%1000), account.statusesCount));
|
||||
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(100), V.dp(100)));
|
||||
ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.header : account.headerStatic, 1000, 1000));
|
||||
ViewImageLoader.loadWithoutAnimation(avatar, avatar.getDrawable(), new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(100), V.dp(100)));
|
||||
ViewImageLoader.loadWithoutAnimation(cover, cover.getDrawable(), new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.header : account.headerStatic, 1000, 1000));
|
||||
SpannableStringBuilder ssb=new SpannableStringBuilder(account.displayName);
|
||||
if(AccountSessionManager.get(accountID).getLocalPreferences().customEmojiInNames)
|
||||
HtmlParser.parseCustomEmoji(ssb, account.emojis);
|
||||
@@ -763,6 +766,9 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop{
|
||||
})
|
||||
.wrapProgress(getActivity(), R.string.loading, false)
|
||||
.exec(accountID);
|
||||
}else if(id==R.id.copy_link){
|
||||
getActivity().getSystemService(ClipboardManager.class).setPrimaryClip(ClipData.newPlainText(null, account.url));
|
||||
UiUtils.maybeShowTextCopiedToast(getActivity());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1134,7 +1140,7 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop{
|
||||
if(ava==null)
|
||||
return;
|
||||
int radius=V.dp(25);
|
||||
currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(account.avatar, ava), 0,
|
||||
currentPhotoViewer=new PhotoViewer(getActivity(), null, createFakeAttachments(account.avatar, ava), 0,
|
||||
null, accountID, new SingleImagePhotoViewerListener(avatar, avatarBorder, new int[]{radius, radius, radius, radius}, this, ()->currentPhotoViewer=null, ()->ava, null, null));
|
||||
}
|
||||
}
|
||||
@@ -1148,7 +1154,7 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop{
|
||||
Drawable drawable=cover.getDrawable();
|
||||
if(drawable==null || drawable instanceof ColorDrawable || account.headerStatic.endsWith("/missing.png"))
|
||||
return;
|
||||
currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(account.header, drawable), 0,
|
||||
currentPhotoViewer=new PhotoViewer(getActivity(), null, createFakeAttachments(account.header, drawable), 0,
|
||||
null, accountID, new SingleImagePhotoViewerListener(cover, cover, null, this, ()->currentPhotoViewer=null, ()->drawable, ()->avatarBorder.setTranslationZ(2), ()->avatarBorder.setTranslationZ(0)));
|
||||
}
|
||||
}
|
||||
@@ -1171,9 +1177,16 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop{
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data){
|
||||
if(resultCode==Activity.RESULT_OK){
|
||||
if(requestCode==AVATAR_RESULT){
|
||||
editNewAvatar=data.getData();
|
||||
ViewImageLoader.loadWithoutAnimation(avatar, null, new UrlImageLoaderRequest(editNewAvatar, V.dp(100), V.dp(100)));
|
||||
editDirty=true;
|
||||
if(!isTablet){
|
||||
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||
}
|
||||
int radius=V.dp(25);
|
||||
new AvatarCropper(getActivity(), data.getData(), new SingleImagePhotoViewerListener(avatar, avatarBorder, new int[]{radius, radius, radius, radius}, this, ()->{}, null, null, null), (thumbnail, uri)->{
|
||||
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
|
||||
avatar.setImageDrawable(thumbnail);
|
||||
editNewAvatar=uri;
|
||||
editDirty=true;
|
||||
}, ()->getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)).show();
|
||||
}else if(requestCode==COVER_RESULT){
|
||||
editNewCover=data.getData();
|
||||
ViewImageLoader.loadWithoutAnimation(cover, null, new UrlImageLoaderRequest(editNewCover, V.dp(1000), V.dp(1000)));
|
||||
@@ -1206,6 +1219,13 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop{
|
||||
return actionButton.getVisibility()==View.VISIBLE && actionButtonWrap.getTop()+actionButtonWrap.getHeight()>scrollView.getScrollY();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvideAssistContent(AssistContent content){
|
||||
if(account!=null){
|
||||
content.setWebUri(Uri.parse(account.url));
|
||||
}
|
||||
}
|
||||
|
||||
private class ProfilePagerAdapter extends RecyclerView.Adapter<SimpleViewHolder>{
|
||||
@NonNull
|
||||
@Override
|
||||
|
||||
@@ -243,7 +243,7 @@ public class SplashFragment extends AppKitFragment{
|
||||
|
||||
@Override
|
||||
public boolean wantsLightNavigationBar(){
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.assist.AssistContent;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
@@ -46,7 +48,7 @@ import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class ThreadFragment extends StatusListFragment{
|
||||
public class ThreadFragment extends StatusListFragment implements AssistContentProviderFragment{
|
||||
private Status mainStatus;
|
||||
private ImageView endMark;
|
||||
private FrameLayout replyContainer;
|
||||
@@ -260,6 +262,11 @@ public class ThreadFragment extends StatusListFragment{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvideAssistContent(AssistContent content){
|
||||
content.setWebUri(Uri.parse(mainStatus.url));
|
||||
}
|
||||
|
||||
private class ReplyLinesItemDecoration extends RecyclerView.ItemDecoration{
|
||||
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@@ -24,7 +25,9 @@ import org.joinmastodon.android.model.AccountField;
|
||||
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.SingleImagePhotoViewerListener;
|
||||
import org.joinmastodon.android.ui.adapters.GenericListItemsAdapter;
|
||||
import org.joinmastodon.android.ui.photoviewer.AvatarCropper;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.viewholders.ListItemViewHolder;
|
||||
import org.joinmastodon.android.ui.views.ReorderableLinearLayout;
|
||||
@@ -53,6 +56,7 @@ public class OnboardingProfileSetupFragment extends ToolbarFragment{
|
||||
private Uri avatarUri, coverUri;
|
||||
private LinearLayout scrollContent;
|
||||
private CheckableListItem<Void> discoverableItem;
|
||||
private View avaBorder;
|
||||
|
||||
private static final int AVATAR_RESULT=348;
|
||||
private static final int COVER_RESULT=183;
|
||||
@@ -80,6 +84,7 @@ public class OnboardingProfileSetupFragment extends ToolbarFragment{
|
||||
bioEdit=view.findViewById(R.id.bio);
|
||||
avaImage=view.findViewById(R.id.avatar);
|
||||
coverImage=view.findViewById(R.id.header);
|
||||
avaBorder=view.findViewById(R.id.avatar_border);
|
||||
|
||||
btn=view.findViewById(R.id.btn_next);
|
||||
btn.setOnClickListener(v->onButtonClick());
|
||||
@@ -152,20 +157,25 @@ public class OnboardingProfileSetupFragment extends ToolbarFragment{
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data){
|
||||
if(resultCode!=Activity.RESULT_OK)
|
||||
return;
|
||||
ImageView img;
|
||||
Uri uri=data.getData();
|
||||
int size;
|
||||
if(requestCode==AVATAR_RESULT){
|
||||
img=avaImage;
|
||||
avatarUri=uri;
|
||||
size=V.dp(100);
|
||||
if(!isTablet){
|
||||
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||
}
|
||||
int radius=V.dp(25);
|
||||
new AvatarCropper(getActivity(), data.getData(), new SingleImagePhotoViewerListener(avaImage, avaBorder, new int[]{radius, radius, radius, radius}, this, ()->{}, null, null, null), (thumbnail, newUri)->{
|
||||
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
|
||||
avaImage.setImageDrawable(thumbnail);
|
||||
avaImage.setForeground(null);
|
||||
avatarUri=newUri;
|
||||
}, ()->getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)).show();
|
||||
}else{
|
||||
img=coverImage;
|
||||
coverUri=uri;
|
||||
size=V.dp(1000);
|
||||
ViewImageLoader.load(coverImage, null, new UrlImageLoaderRequest(uri, size, size));
|
||||
coverImage.setForeground(null);
|
||||
}
|
||||
img.setForeground(null);
|
||||
ViewImageLoader.load(img, null, new UrlImageLoaderRequest(uri, size, size));
|
||||
}
|
||||
|
||||
private void showDiscoverabilityAlert(){
|
||||
|
||||
@@ -2,10 +2,16 @@ package org.joinmastodon.android.fragments.settings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.util.Pair;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.PushSubscriptionManager;
|
||||
import org.joinmastodon.android.api.session.AccountActivationInfo;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
@@ -14,12 +20,22 @@ import org.joinmastodon.android.fragments.HomeFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
||||
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
|
||||
import org.joinmastodon.android.model.viewmodel.ListItem;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.palette.graphics.Palette;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class SettingsDebugFragment extends BaseSettingsFragment<Void>{
|
||||
private CheckableListItem<Void> donationsStagingItem;
|
||||
@@ -37,7 +53,9 @@ public class SettingsDebugFragment extends BaseSettingsFragment<Void>{
|
||||
new ListItem<>("Reset search info banners", null, this::onResetDiscoverBannersClick),
|
||||
new ListItem<>("Reset pre-reply sheets", null, this::onResetPreReplySheetsClick),
|
||||
new ListItem<>("Clear dismissed donation campaigns", null, this::onClearDismissedCampaignsClick),
|
||||
donationsStagingItem=new CheckableListItem<>("Use staging environment for donations", "Restart app to apply", CheckableListItem.Style.SWITCH, getPrefs().getBoolean("donationsStaging", false), this::toggleCheckableItem)
|
||||
donationsStagingItem=new CheckableListItem<>("Use staging environment for donations", "Restart app to apply", CheckableListItem.Style.SWITCH, getPrefs().getBoolean("donationsStaging", false), this::toggleCheckableItem),
|
||||
new ListItem<>("Delete cached instance info", null, this::onDeleteInstanceInfoClick),
|
||||
new ListItem<>("View dynamic color values", null, this::onViewColorsClick)
|
||||
));
|
||||
if(!GithubSelfUpdater.needSelfUpdating()){
|
||||
resetUpdateItem.isEnabled=selfUpdateItem.isEnabled=false;
|
||||
@@ -95,6 +113,63 @@ public class SettingsDebugFragment extends BaseSettingsFragment<Void>{
|
||||
Toast.makeText(getActivity(), "Dismissed campaigns cleared. Restart app to see your current campaign, if any", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
private void onDeleteInstanceInfoClick(ListItem<?> item){
|
||||
AccountSessionManager.getInstance().clearInstanceInfo();
|
||||
Toast.makeText(getActivity(), "Instances removed from database", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
private void onViewColorsClick(ListItem<?> item){
|
||||
ArrayList<Pair<Integer, String>> attrs=new ArrayList<>();
|
||||
Field[] fields=R.attr.class.getFields();
|
||||
try{
|
||||
for(Field fld:fields){
|
||||
if(fld.getName().startsWith("color") && fld.getType().equals(int.class)){
|
||||
attrs.add(new Pair<>((Integer)fld.get(null), fld.getName()));
|
||||
}
|
||||
}
|
||||
}catch(IllegalAccessException x){
|
||||
Toast.makeText(getActivity(), x.toString(), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
class ColorsAdapter extends RecyclerView.Adapter<SimpleViewHolder>{
|
||||
@NonNull
|
||||
@Override
|
||||
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
TextView view=new TextView(getActivity());
|
||||
int pad=V.dp(16);
|
||||
view.setPadding(pad, pad, pad, pad);
|
||||
view.setTextSize(14);
|
||||
view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
return new SimpleViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
|
||||
Pair<Integer, String> attr=attrs.get(position);
|
||||
TextView view=(TextView) holder.itemView;
|
||||
int color=UiUtils.getThemeColor(getActivity(), attr.first);
|
||||
view.setBackgroundColor(color);
|
||||
view.setText(String.format("%s\n#%06X", attr.second, (color & 0xFF000000) != 0xFF000000 ? color : (color & 0xFFFFFF)));
|
||||
view.setTextColor(new Palette.Swatch(color | 0xFF000000, 1).getBodyTextColor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return attrs.size();
|
||||
}
|
||||
}
|
||||
|
||||
RecyclerView rv=new RecyclerView(getActivity());
|
||||
rv.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
rv.setAdapter(new ColorsAdapter());
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle("Dynamic colors")
|
||||
.setView(rv)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void restartUI(){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.joinmastodon.android.model.viewmodel.CheckableListItem;
|
||||
import org.joinmastodon.android.model.viewmodel.ListItem;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@@ -29,7 +30,7 @@ import me.grishka.appkit.FragmentStackActivity;
|
||||
public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
|
||||
private ImageView themeTransitionWindowView;
|
||||
private ListItem<Void> themeItem;
|
||||
private CheckableListItem<Void> showCWsItem, hideSensitiveMediaItem, interactionCountsItem, emojiInNamesItem;
|
||||
private CheckableListItem<Void> showCWsItem, hideSensitiveMediaItem, interactionCountsItem, emojiInNamesItem, dynamicColorsItem;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -37,13 +38,20 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
|
||||
setTitle(R.string.settings_display);
|
||||
AccountSession s=AccountSessionManager.get(accountID);
|
||||
AccountLocalPreferences lp=s.getLocalPreferences();
|
||||
onDataLoaded(List.of(
|
||||
themeItem=new ListItem<>(R.string.settings_theme, getAppearanceValue(), R.drawable.ic_dark_mode_24px, this::onAppearanceClick),
|
||||
showCWsItem=new CheckableListItem<>(R.string.settings_show_cws, 0, CheckableListItem.Style.SWITCH, lp.showCWs, R.drawable.ic_warning_24px, this::toggleCheckableItem),
|
||||
hideSensitiveMediaItem=new CheckableListItem<>(R.string.settings_hide_sensitive_media, 0, CheckableListItem.Style.SWITCH, lp.hideSensitiveMedia, R.drawable.ic_no_adult_content_24px, this::toggleCheckableItem),
|
||||
interactionCountsItem=new CheckableListItem<>(R.string.settings_show_interaction_counts, 0, CheckableListItem.Style.SWITCH, lp.showInteractionCounts, R.drawable.ic_social_leaderboard_24px, this::toggleCheckableItem),
|
||||
emojiInNamesItem=new CheckableListItem<>(R.string.settings_show_emoji_in_names, 0, CheckableListItem.Style.SWITCH, lp.customEmojiInNames, R.drawable.ic_emoticon_24px, this::toggleCheckableItem)
|
||||
));
|
||||
List<ListItem<Void>> items=new ArrayList<>();
|
||||
items.add(themeItem=new ListItem<>(R.string.settings_theme, getAppearanceValue(), R.drawable.ic_dark_mode_24px, this::onAppearanceClick));
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.S){
|
||||
items.add(dynamicColorsItem=new CheckableListItem<>(R.string.settings_use_dynamic_colors, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.useDynamicColors, R.drawable.ic_palette_24px, item->{
|
||||
toggleCheckableItem(item);
|
||||
setUseDynamicColors(item.checked);
|
||||
}));
|
||||
dynamicColorsItem.checkedChangeListener=this::setUseDynamicColors;
|
||||
}
|
||||
items.add(showCWsItem=new CheckableListItem<>(R.string.settings_show_cws, 0, CheckableListItem.Style.SWITCH, lp.showCWs, R.drawable.ic_warning_24px, this::toggleCheckableItem));
|
||||
items.add(hideSensitiveMediaItem=new CheckableListItem<>(R.string.settings_hide_sensitive_media, 0, CheckableListItem.Style.SWITCH, lp.hideSensitiveMedia, R.drawable.ic_no_adult_content_24px, this::toggleCheckableItem));
|
||||
items.add(interactionCountsItem=new CheckableListItem<>(R.string.settings_show_interaction_counts, 0, CheckableListItem.Style.SWITCH, lp.showInteractionCounts, R.drawable.ic_social_leaderboard_24px, this::toggleCheckableItem));
|
||||
items.add(emojiInNamesItem=new CheckableListItem<>(R.string.settings_show_emoji_in_names, 0, CheckableListItem.Style.SWITCH, lp.customEmojiInNames, R.drawable.ic_emoticon_24px, this::toggleCheckableItem));
|
||||
onDataLoaded(items);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -109,6 +117,13 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
|
||||
.show();
|
||||
}
|
||||
|
||||
private void setUseDynamicColors(boolean useDynamicColors){
|
||||
dynamicColorsItem.checked=useDynamicColors;
|
||||
GlobalUserPreferences.useDynamicColors=useDynamicColors;
|
||||
GlobalUserPreferences.save();
|
||||
restartActivityToApplyNewTheme();
|
||||
}
|
||||
|
||||
private void maybeApplyNewThemeRightNow(GlobalUserPreferences.ThemePreference prev){
|
||||
boolean isCurrentDark=prev==GlobalUserPreferences.ThemePreference.DARK ||
|
||||
(prev==GlobalUserPreferences.ThemePreference.AUTO && Build.VERSION.SDK_INT>=30 && getResources().getConfiguration().isNightModeActive());
|
||||
|
||||
@@ -27,10 +27,10 @@ public enum NotificationType{
|
||||
MODERATION_WARNING;
|
||||
|
||||
public boolean canBeGrouped(){
|
||||
return this==REBLOG || this==FAVORITE;
|
||||
return this==REBLOG || this==FAVORITE || this==FOLLOW;
|
||||
}
|
||||
|
||||
public static EnumSet<NotificationType> getGroupableTypes(){
|
||||
return EnumSet.of(FAVORITE, REBLOG);
|
||||
return EnumSet.of(FAVORITE, REBLOG, FOLLOW);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.joinmastodon.android.ui;
|
||||
|
||||
public enum ColorContrastMode{
|
||||
DEFAULT,
|
||||
MEDIUM,
|
||||
HIGH;
|
||||
|
||||
public static ColorContrastMode fromContrastValue(float value){
|
||||
if(value>0.75f)
|
||||
return HIGH;
|
||||
if(value>0.25f)
|
||||
return MEDIUM;
|
||||
return DEFAULT;
|
||||
}
|
||||
}
|
||||
@@ -216,7 +216,7 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{
|
||||
}
|
||||
}
|
||||
|
||||
private class EmojiViewHolder extends BindableViewHolder<Emoji> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable{
|
||||
private class EmojiViewHolder extends BindableViewHolder<Emoji> implements ImageLoaderViewHolder{
|
||||
public int positionWithinCategory;
|
||||
public EmojiViewHolder(){
|
||||
super(new ImageView(activity));
|
||||
@@ -226,6 +226,7 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{
|
||||
int pad=V.dp(12);
|
||||
img.setPadding(pad, pad, pad, pad);
|
||||
img.setBackgroundResource(R.drawable.bg_custom_emoji);
|
||||
itemView.setOnClickListener(v->onClick());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -247,8 +248,7 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{
|
||||
((ImageView)itemView).setImageDrawable(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
private void onClick(){
|
||||
listener.onEmojiSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ import org.joinmastodon.android.ui.drawables.SawtoothTearDrawable;
|
||||
// Mind the gap!
|
||||
public class GapStatusDisplayItem extends StatusDisplayItem{
|
||||
public boolean loading;
|
||||
public boolean enteredFromTop; // While the user was scrolling, did the gap item pop out from behind the top edge of the list?
|
||||
public boolean visible; // Is this item currently within the viewport of the RecyclerView (and has a bound view)?
|
||||
|
||||
public GapStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment){
|
||||
super(parentID, parentFragment);
|
||||
|
||||
@@ -73,6 +73,7 @@ public class NotificationHeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
text=parentFragment.getResources().getQuantityString(switch(notification.notification.type){
|
||||
case FAVORITE -> R.plurals.user_and_x_more_favorited;
|
||||
case REBLOG -> R.plurals.user_and_x_more_boosted;
|
||||
case FOLLOW -> R.plurals.user_and_x_more_followed;
|
||||
default -> throw new IllegalStateException("Unexpected value: " + notification.notification.type);
|
||||
}, notification.notification.notificationsCount-1, "{{name}}", notification.notification.notificationsCount-1);
|
||||
}else if(notification.notification.type==NotificationType.POLL){
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package org.joinmastodon.android.ui.drawables;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
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.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class VideoPlayerSeekBarThumbDrawable extends Drawable{
|
||||
private Paint thumbPaint=new Paint(Paint.ANTI_ALIAS_FLAG), clearPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private Path clearPath=new Path();
|
||||
|
||||
public VideoPlayerSeekBarThumbDrawable(){
|
||||
thumbPaint.setColor(0xffffffff);
|
||||
clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
|
||||
clearPath.addRect(0, 0, V.dp(20), V.dp(32), Path.Direction.CW);
|
||||
Path tmp=new Path();
|
||||
float radius=V.dp(2);
|
||||
tmp.addRoundRect(V.dp(-2), V.dp(12), V.dp(2), V.dp(20), radius, radius, Path.Direction.CW);
|
||||
tmp.addRoundRect(V.dp(18), V.dp(12), V.dp(22), V.dp(20), radius, radius, Path.Direction.CW);
|
||||
clearPath.op(tmp, Path.Op.DIFFERENCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas){
|
||||
Rect bounds=getBounds();
|
||||
int thumbWidth=V.dp(4), thumbHeight=V.dp(32);
|
||||
int thumbX=bounds.centerX()-thumbWidth/2, thumbY=bounds.centerY()-thumbHeight/2;
|
||||
canvas.save();
|
||||
canvas.translate(thumbX-V.dp(8), thumbY);
|
||||
canvas.drawPath(clearPath, clearPaint);
|
||||
canvas.restore();
|
||||
canvas.drawRoundRect(thumbX, thumbY, thumbX+thumbWidth, thumbY+thumbHeight, V.dp(2), V.dp(2), thumbPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable ColorFilter colorFilter){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity(){
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth(){
|
||||
return V.dp(8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight(){
|
||||
return V.dp(32);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
package org.joinmastodon.android.ui.photoviewer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
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.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toast;
|
||||
import android.window.OnBackInvokedDispatcher;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.WindowRootFrameLayout;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
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;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
|
||||
public class AvatarCropper implements ZoomPanView.Listener{
|
||||
private Activity activity;
|
||||
private Context context;
|
||||
private WindowManager wm;
|
||||
private WindowRootFrameLayout windowView;
|
||||
private FragmentRootLinearLayout overlay;
|
||||
private ZoomPanView zoomPanView;
|
||||
private ImageButton closeButton;
|
||||
private ImageView image;
|
||||
private View confirmButton;
|
||||
private Runnable onCancel;
|
||||
private OnCropChosenListener cropChosenListener;
|
||||
private Uri originalUri;
|
||||
private PhotoViewer.Listener listener;
|
||||
private Drawable background=new ColorDrawable(0xff000000);
|
||||
|
||||
public AvatarCropper(Activity activity, Uri imageUri, PhotoViewer.Listener photoViewerListener, OnCropChosenListener cropChosenListener, Runnable onCancel){
|
||||
this.activity=activity;
|
||||
this.context=new ContextThemeWrapper(activity, UiUtils.getThemeForUserPreference(activity, GlobalUserPreferences.ThemePreference.DARK));
|
||||
originalUri=imageUri;
|
||||
wm=context.getSystemService(WindowManager.class);
|
||||
this.cropChosenListener=cropChosenListener;
|
||||
this.onCancel=onCancel;
|
||||
this.listener=photoViewerListener;
|
||||
|
||||
windowView=(WindowRootFrameLayout) LayoutInflater.from(this.context).inflate(R.layout.avatar_cropper, null);
|
||||
overlay=windowView.findViewById(R.id.overlay);
|
||||
closeButton=windowView.findViewById(R.id.btn_back);
|
||||
zoomPanView=windowView.findViewById(R.id.zoom_pan_view);
|
||||
image=windowView.findViewById(R.id.image);
|
||||
confirmButton=windowView.findViewById(R.id.btn_confirm);
|
||||
|
||||
windowView.setBackground(background);
|
||||
windowView.setDispatchApplyWindowInsetsListener((v, insets)->{
|
||||
int bottomInset=0;
|
||||
if(Build.VERSION.SDK_INT>=27){
|
||||
int inset=insets.getSystemWindowInsetBottom();
|
||||
bottomInset=inset>0 ? Math.max(inset, V.dp(24)) : 0;
|
||||
}
|
||||
((FrameLayout.LayoutParams)confirmButton.getLayoutParams()).bottomMargin=bottomInset+V.dp(16+80);
|
||||
return overlay.dispatchApplyWindowInsets(insets);
|
||||
});
|
||||
windowView.setDispatchKeyEventListener((v, keyCode, event)->{
|
||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.TIRAMISU && event.getKeyCode()==KeyEvent.KEYCODE_BACK){
|
||||
if(event.getAction()==KeyEvent.ACTION_DOWN){
|
||||
dismiss(true, onCancel);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
closeButton.setOnClickListener(v->dismiss(true, onCancel));
|
||||
overlay.setStatusBarColor(0);
|
||||
overlay.setNavigationBarColor(0);
|
||||
overlay.setBackground(new OverlayDrawable());
|
||||
zoomPanView.setListener(this);
|
||||
zoomPanView.setFill(true);
|
||||
zoomPanView.setSwipeToDismissEnabled(false);
|
||||
ViewImageLoader.load(new ViewImageLoader.Target(){
|
||||
@Override
|
||||
public void setImageDrawable(Drawable d){
|
||||
if(d!=null){
|
||||
image.setImageDrawable(d);
|
||||
image.setLayoutParams(new FrameLayout.LayoutParams(d.getIntrinsicWidth(), d.getIntrinsicHeight(), Gravity.CENTER));
|
||||
zoomPanView.updateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(){
|
||||
return image;
|
||||
}
|
||||
}, null, new UrlImageLoaderRequest(Bitmap.Config.ARGB_8888, 0, 0, List.of(), imageUri), false);
|
||||
windowView.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom)->{
|
||||
if(left==oldLeft && top==oldTop && right==oldRight && bottom==oldBottom)
|
||||
return;
|
||||
int width=right-left;
|
||||
int height=bottom-top;
|
||||
int size=V.dp(192);
|
||||
int hpad=(width-size)/2;
|
||||
int vpad=(height-size)/2;
|
||||
zoomPanView.setPadding(hpad, vpad, hpad, vpad);
|
||||
zoomPanView.updateLayout();
|
||||
});
|
||||
confirmButton.setOnClickListener(v->confirm());
|
||||
}
|
||||
|
||||
public void show(){
|
||||
WindowManager.LayoutParams wlp=new WindowManager.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT);
|
||||
wlp.type=WindowManager.LayoutParams.TYPE_APPLICATION;
|
||||
wlp.flags=WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
|
||||
| WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
|
||||
wlp.format=PixelFormat.TRANSLUCENT;
|
||||
wlp.setTitle(context.getString(R.string.avatar_move_and_scale));
|
||||
if(Build.VERSION.SDK_INT>=28)
|
||||
wlp.layoutInDisplayCutoutMode=Build.VERSION.SDK_INT>=30 ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS : WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
||||
windowView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
||||
wm.addView(windowView, wlp);
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.TIRAMISU){
|
||||
windowView.findOnBackInvokedDispatcher().registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, ()->dismiss(true, onCancel));
|
||||
}
|
||||
}
|
||||
|
||||
public void dismiss(boolean animated, Runnable onDone){
|
||||
if(animated){
|
||||
windowView.animate()
|
||||
.alpha(0)
|
||||
.setDuration(250)
|
||||
.setInterpolator(CubicBezierInterpolator.DEFAULT)
|
||||
.withEndAction(()->{
|
||||
wm.removeView(windowView);
|
||||
if(onDone!=null)
|
||||
onDone.run();
|
||||
})
|
||||
.start();
|
||||
}else{
|
||||
wm.removeView(windowView);
|
||||
if(onDone!=null)
|
||||
onDone.run();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionAnimationUpdate(float translateX, float translateY, float scale){
|
||||
listener.setTransitioningViewTransform(translateX, translateY, scale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionAnimationFinished(){
|
||||
listener.endPhotoViewTransition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetBackgroundAlpha(float alpha){
|
||||
background.setAlpha(Math.round(255*alpha));
|
||||
overlay.setAlpha(alpha);
|
||||
confirmButton.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartSwipeToDismiss(){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartSwipeToDismissTransition(float velocityY){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwipeToDismissCanceled(){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismissed(){
|
||||
listener.setPhotoViewVisibility(0, true);
|
||||
wm.removeView(windowView);
|
||||
listener.photoViewerDismissed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSingleTap(){
|
||||
|
||||
}
|
||||
|
||||
private void confirm(){
|
||||
// stop receiving input events to allow the user to interact with the underlying UI while the animation is still running
|
||||
WindowManager.LayoutParams wlp=(WindowManager.LayoutParams) windowView.getLayoutParams();
|
||||
wlp.flags|=WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
|
||||
windowView.setSystemUiVisibility(windowView.getSystemUiVisibility() | (activity.getWindow().getDecorView().getSystemUiVisibility() & (View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR)));
|
||||
wm.updateViewLayout(windowView, wlp);
|
||||
|
||||
Drawable drawable=image.getDrawable();
|
||||
zoomPanView.endAllAnimations();
|
||||
Rect rect=new Rect();
|
||||
image.getHitRect(rect);
|
||||
float scale=image.getScaleX();
|
||||
int x=Math.round((zoomPanView.getPaddingLeft()-rect.left)/scale);
|
||||
int y=Math.round((zoomPanView.getPaddingTop()-rect.top)/scale);
|
||||
int size=Math.round(V.dp(192)/scale);
|
||||
if(x==0 && y==0 && drawable.getIntrinsicWidth()==drawable.getIntrinsicHeight() && size==drawable.getIntrinsicWidth()){
|
||||
dismissWithTransition();
|
||||
cropChosenListener.onCropChosen(drawable, originalUri);
|
||||
return;
|
||||
}
|
||||
|
||||
Bitmap croppedBitmap=Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
|
||||
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
||||
Canvas c=new Canvas(croppedBitmap);
|
||||
c.translate(-x, -y);
|
||||
drawable.draw(c);
|
||||
|
||||
MastodonAPIController.runInBackground(()->{
|
||||
String mimetype;
|
||||
if("file".equals(originalUri.getScheme())){
|
||||
mimetype=UiUtils.getFileMediaType(new File(originalUri.getPath())).type();
|
||||
}else{
|
||||
mimetype=activity.getContentResolver().getType(originalUri);
|
||||
}
|
||||
if(mimetype==null)
|
||||
mimetype="image/jpeg";
|
||||
Bitmap.CompressFormat format=switch(mimetype){
|
||||
case "image/png", "image/gif" -> Bitmap.CompressFormat.PNG;
|
||||
default -> Bitmap.CompressFormat.JPEG;
|
||||
};
|
||||
File outputFile=new File(activity.getCacheDir(), "avatar_upload."+(format==Bitmap.CompressFormat.PNG ? "png" : "jpg"));
|
||||
try(FileOutputStream out=new FileOutputStream(outputFile)){
|
||||
croppedBitmap.compress(format, 97, out);
|
||||
}catch(IOException e){
|
||||
activity.runOnUiThread(()->{
|
||||
Toast.makeText(activity, R.string.error_saving_file, Toast.LENGTH_SHORT).show();
|
||||
dismiss(true, onCancel);
|
||||
});
|
||||
return;
|
||||
}
|
||||
outputFile.deleteOnExit();
|
||||
activity.runOnUiThread(()->{
|
||||
image.setImageBitmap(croppedBitmap);
|
||||
image.getLayoutParams().width=image.getLayoutParams().height=size;
|
||||
zoomPanView.updateLayout();
|
||||
cropChosenListener.onCropChosen(new BitmapDrawable(croppedBitmap), Uri.fromFile(outputFile));
|
||||
dismissWithTransition();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void dismissWithTransition(){
|
||||
zoomPanView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||
@Override
|
||||
public boolean onPreDraw(){
|
||||
zoomPanView.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
|
||||
listener.setPhotoViewVisibility(0, true);
|
||||
int[] radius=new int[4];
|
||||
Rect rect=new Rect();
|
||||
if(listener.startPhotoViewTransition(0, rect, radius)){
|
||||
zoomPanView.animateOut(rect, radius, 0);
|
||||
}else{
|
||||
windowView.animate()
|
||||
.alpha(0)
|
||||
.setDuration(300)
|
||||
.setInterpolator(CubicBezierInterpolator.DEFAULT)
|
||||
.withEndAction(AvatarCropper.this::onDismissed)
|
||||
.start();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static class OverlayDrawable extends Drawable{
|
||||
private Path path=new Path(), tmpPath=new Path();
|
||||
private Paint overlayPaint=new Paint(Paint.ANTI_ALIAS_FLAG), strokePaint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
public OverlayDrawable(){
|
||||
overlayPaint.setColor(0xb3000000);
|
||||
strokePaint.setColor(0x4dffffff);
|
||||
strokePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
|
||||
strokePaint.setStyle(Paint.Style.STROKE);
|
||||
strokePaint.setStrokeWidth(V.dp(1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas){
|
||||
canvas.drawPath(path, overlayPaint);
|
||||
|
||||
Rect bounds=getBounds();
|
||||
float size=V.dp(192)-strokePaint.getStrokeWidth();
|
||||
float x=bounds.centerX()-size/2;
|
||||
float y=bounds.centerY()-size/2;
|
||||
float radius=V.dp(40)-strokePaint.getStrokeWidth()/2f;
|
||||
canvas.drawRoundRect(x, y, x+size, y+size, radius, radius, strokePaint);
|
||||
}
|
||||
|
||||
@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){
|
||||
path.rewind();
|
||||
path.addRect(bounds.left, bounds.top, bounds.right, bounds.bottom, Path.Direction.CW);
|
||||
tmpPath.rewind();
|
||||
int size=V.dp(192);
|
||||
int x=bounds.centerX()-size/2;
|
||||
int y=bounds.centerY()-size/2;
|
||||
tmpPath.addRoundRect(x, y, x+size, y+size, V.dp(40), V.dp(40), Path.Direction.CW);
|
||||
path.op(tmpPath, Path.Op.DIFFERENCE);
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnCropChosenListener{
|
||||
void onCropChosen(Drawable thumbnail, Uri uri);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,10 @@ import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Insets;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
@@ -28,17 +31,18 @@ import android.media.MediaPlayer;
|
||||
import android.media.MediaScannerConnection;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.SystemClock;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Property;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.DisplayCutout;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MenuItem;
|
||||
import android.view.Surface;
|
||||
import android.view.TextureView;
|
||||
import android.view.View;
|
||||
@@ -53,18 +57,28 @@ import android.widget.ProgressBar;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.Toolbar;
|
||||
import android.window.OnBackInvokedDispatcher;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.Snackbar;
|
||||
import org.joinmastodon.android.ui.drawables.VideoPlayerSeekBarThumbDrawable;
|
||||
import org.joinmastodon.android.ui.utils.BlurHashDecoder;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.WindowRootFrameLayout;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
@@ -77,14 +91,17 @@ import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.palette.graphics.ColorUtils;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.imageloader.ImageCache;
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.BottomSheet;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
import okio.BufferedSink;
|
||||
import okio.Okio;
|
||||
@@ -97,31 +114,38 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
|
||||
private Activity activity;
|
||||
private List<Attachment> attachments;
|
||||
private int[] backgroundColors;
|
||||
private int currentIndex;
|
||||
private WindowManager wm;
|
||||
private Listener listener;
|
||||
private Status status;
|
||||
private String accountID;
|
||||
private BaseStatusListFragment<?> parentFragment;
|
||||
|
||||
private FrameLayout windowView;
|
||||
private WindowRootFrameLayout windowView;
|
||||
private FragmentRootLinearLayout uiOverlay;
|
||||
private ViewPager2 pager;
|
||||
private ColorDrawable background=new ColorDrawable(0xff000000);
|
||||
private ArrayList<MediaPlayer> players=new ArrayList<>();
|
||||
private int screenOnRefCount=0;
|
||||
private Toolbar toolbar;
|
||||
private View toolbarWrap;
|
||||
private SeekBar videoSeekBar;
|
||||
private TextView videoTimeView;
|
||||
private ImageButton videoPlayPauseButton;
|
||||
private View videoControls;
|
||||
private TextView altText;
|
||||
private ImageButton backButton, downloadButton;
|
||||
private View bottomBar;
|
||||
private View postActions;
|
||||
private View replyBtn, boostBtn, favoriteBtn, shareBtn, bookmarkBtn;
|
||||
private TextView replyText, boostText, favoriteText;
|
||||
private boolean uiVisible=true;
|
||||
private AudioManager.OnAudioFocusChangeListener audioFocusListener=this::onAudioFocusChanged;
|
||||
private Runnable uiAutoHider=()->{
|
||||
if(uiVisible)
|
||||
toggleUI();
|
||||
};
|
||||
private Animator currentSheetRelatedToolbarAnimation;
|
||||
private Animator currentUiVisibilityAnimation;
|
||||
|
||||
private boolean videoPositionNeedsUpdating;
|
||||
private Runnable videoPositionUpdater=this::updateVideoPosition;
|
||||
@@ -157,53 +181,63 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
}
|
||||
};
|
||||
|
||||
public PhotoViewer(Activity activity, List<Attachment> attachments, int index, Status status, String accountID, Listener listener){
|
||||
public PhotoViewer(Activity activity, BaseStatusListFragment<?> parentFragment, List<Attachment> attachments, int index, Status status, String accountID, Listener listener){
|
||||
this.activity=activity;
|
||||
this.attachments=attachments.stream().filter(a->a.type==Attachment.Type.IMAGE || a.type==Attachment.Type.GIFV || a.type==Attachment.Type.VIDEO).collect(Collectors.toList());
|
||||
currentIndex=index;
|
||||
this.listener=listener;
|
||||
this.status=status;
|
||||
this.accountID=accountID;
|
||||
this.parentFragment=parentFragment;
|
||||
|
||||
backgroundColors=new int[this.attachments.size()];
|
||||
int i=0;
|
||||
float[] hsl=new float[3];
|
||||
for(Attachment att:this.attachments){
|
||||
if(TextUtils.isEmpty(att.blurhash)){
|
||||
backgroundColors[i]=0xff000000;
|
||||
}else{
|
||||
ColorUtils.colorToHSL(BlurHashDecoder.decodeToSingleColor(att.blurhash) | 0xff000000, hsl);
|
||||
hsl[2]=Math.min(hsl[2], 0.15f);
|
||||
backgroundColors[i]=ColorUtils.HSLToColor(hsl);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
wm=activity.getWindowManager();
|
||||
|
||||
windowView=new FrameLayout(activity){
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event){
|
||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.TIRAMISU && event.getKeyCode()==KeyEvent.KEYCODE_BACK){
|
||||
if(event.getAction()==KeyEvent.ACTION_DOWN){
|
||||
onStartSwipeToDismissTransition(0f);
|
||||
}
|
||||
return true;
|
||||
windowView=new WindowRootFrameLayout(activity);
|
||||
windowView.setDispatchKeyEventListener((v, keyCode, event)->{
|
||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.TIRAMISU && event.getKeyCode()==KeyEvent.KEYCODE_BACK){
|
||||
if(event.getAction()==KeyEvent.ACTION_DOWN){
|
||||
onStartSwipeToDismissTransition(0f);
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets){
|
||||
if(Build.VERSION.SDK_INT>=29){
|
||||
DisplayCutout cutout=insets.getDisplayCutout();
|
||||
Insets tappable=insets.getTappableElementInsets();
|
||||
if(cutout!=null){
|
||||
// Make controls extend beneath the cutout, and replace insets to avoid cutout insets being filled with "navigation bar color"
|
||||
int leftInset=Math.max(0, cutout.getSafeInsetLeft()-tappable.left);
|
||||
int rightInset=Math.max(0, cutout.getSafeInsetRight()-tappable.right);
|
||||
toolbarWrap.setPadding(leftInset, 0, rightInset, 0);
|
||||
videoControls.setPadding(leftInset, 0, rightInset, 0);
|
||||
}else{
|
||||
toolbarWrap.setPadding(0, 0, 0, 0);
|
||||
videoControls.setPadding(0, 0, 0, 0);
|
||||
}
|
||||
insets=insets.replaceSystemWindowInsets(tappable.left, tappable.top, tappable.right, tappable.bottom);
|
||||
return false;
|
||||
});
|
||||
windowView.setDispatchApplyWindowInsetsListener((v, insets)->{
|
||||
int bottomInset=insets.getSystemWindowInsetBottom();
|
||||
bottomBar.setPadding(bottomBar.getPaddingLeft(), bottomBar.getPaddingTop(), bottomBar.getPaddingRight(), bottomInset>0 ? Math.max(bottomInset+V.dp(8), V.dp(40)) : V.dp(12));
|
||||
insets=insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), 0);
|
||||
if(Build.VERSION.SDK_INT>=29){
|
||||
DisplayCutout cutout=insets.getDisplayCutout();
|
||||
Insets tappable=insets.getTappableElementInsets();
|
||||
if(cutout!=null){
|
||||
// Make controls extend beneath the cutout, and replace insets to avoid cutout insets being filled with "navigation bar color"
|
||||
int leftInset=Math.max(0, cutout.getSafeInsetLeft()-tappable.left);
|
||||
int rightInset=Math.max(0, cutout.getSafeInsetRight()-tappable.right);
|
||||
toolbarWrap.setPadding(leftInset, 0, rightInset, 0);
|
||||
bottomBar.setPadding(leftInset, bottomBar.getPaddingTop(), rightInset, bottomBar.getPaddingBottom());
|
||||
}else{
|
||||
toolbarWrap.setPadding(0, 0, 0, 0);
|
||||
bottomBar.setPadding(0, bottomBar.getPaddingTop(), 0, bottomBar.getPaddingBottom());
|
||||
}
|
||||
uiOverlay.dispatchApplyWindowInsets(insets);
|
||||
int bottomInset=insets.getSystemWindowInsetBottom();
|
||||
if(bottomInset>0 && bottomInset<V.dp(36)){
|
||||
uiOverlay.setPadding(uiOverlay.getPaddingLeft(), uiOverlay.getPaddingTop(), uiOverlay.getPaddingRight(), V.dp(36));
|
||||
}
|
||||
return insets.consumeSystemWindowInsets();
|
||||
insets=insets.replaceSystemWindowInsets(tappable.left, tappable.top, tappable.right, bottomBar.getVisibility()==View.VISIBLE ? 0 : tappable.bottom);
|
||||
}
|
||||
};
|
||||
uiOverlay.dispatchApplyWindowInsets(insets);
|
||||
return insets.consumeSystemWindowInsets();
|
||||
});
|
||||
windowView.setBackground(background);
|
||||
background.setAlpha(0);
|
||||
pager=new ViewPager2(activity);
|
||||
@@ -214,6 +248,11 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
public void onPageSelected(int position){
|
||||
onPageChanged(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels){
|
||||
updateBackgroundColor(position, positionOffset);
|
||||
}
|
||||
});
|
||||
windowView.addView(pager);
|
||||
pager.setMotionEventSplittingEnabled(false);
|
||||
@@ -222,19 +261,22 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
uiOverlay.setStatusBarColor(0x80000000);
|
||||
uiOverlay.setNavigationBarColor(0x80000000);
|
||||
toolbarWrap=uiOverlay.findViewById(R.id.toolbar_wrap);
|
||||
toolbar=uiOverlay.findViewById(R.id.toolbar);
|
||||
toolbar.setNavigationOnClickListener(v->onStartSwipeToDismissTransition(0));
|
||||
if(status!=null)
|
||||
toolbar.getMenu().add(R.string.info).setIcon(R.drawable.ic_info_24px).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
else
|
||||
toolbar.getMenu().add(R.string.download).setIcon(R.drawable.ic_download_24px).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
toolbar.setOnMenuItemClickListener(item->{
|
||||
if(status!=null)
|
||||
showInfoSheet();
|
||||
else
|
||||
saveCurrentFile();
|
||||
return true;
|
||||
});
|
||||
backButton=uiOverlay.findViewById(R.id.btn_back);
|
||||
backButton.setOnClickListener(v->onStartSwipeToDismissTransition(0));
|
||||
downloadButton=uiOverlay.findViewById(R.id.btn_download);
|
||||
downloadButton.setOnClickListener(v->saveCurrentFile());
|
||||
bottomBar=uiOverlay.findViewById(R.id.bottom_bar);
|
||||
postActions=uiOverlay.findViewById(R.id.post_actions);
|
||||
|
||||
replyBtn=uiOverlay.findViewById(R.id.reply_btn);
|
||||
boostBtn=uiOverlay.findViewById(R.id.boost_btn);
|
||||
favoriteBtn=uiOverlay.findViewById(R.id.favorite_btn);
|
||||
bookmarkBtn=uiOverlay.findViewById(R.id.bookmark_btn);
|
||||
shareBtn=uiOverlay.findViewById(R.id.share_btn);
|
||||
replyText=uiOverlay.findViewById(R.id.reply);
|
||||
boostText=uiOverlay.findViewById(R.id.boost);
|
||||
favoriteText=uiOverlay.findViewById(R.id.favorite);
|
||||
|
||||
uiOverlay.setAlpha(0f);
|
||||
videoControls=uiOverlay.findViewById(R.id.video_player_controls);
|
||||
videoSeekBar=uiOverlay.findViewById(R.id.seekbar);
|
||||
@@ -247,6 +289,25 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
videoLastTimeUpdatePosition=-1;
|
||||
updateVideoTimeText(0);
|
||||
}
|
||||
altText=uiOverlay.findViewById(R.id.alt_text);
|
||||
altText.setOnClickListener(v->showAltTextSheet());
|
||||
updateAltText();
|
||||
updateBackgroundColor(currentIndex, 0);
|
||||
|
||||
if(status==null){
|
||||
bottomBar.setVisibility(View.GONE);
|
||||
}else{
|
||||
Paint paint=new Paint();
|
||||
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
|
||||
postActions.setLayerType(View.LAYER_TYPE_HARDWARE, paint);
|
||||
updatePostActions();
|
||||
|
||||
replyBtn.setOnClickListener(this::onPostActionClick);
|
||||
boostBtn.setOnClickListener(this::onPostActionClick);
|
||||
favoriteBtn.setOnClickListener(this::onPostActionClick);
|
||||
bookmarkBtn.setOnClickListener(this::onPostActionClick);
|
||||
shareBtn.setOnClickListener(this::onPostActionClick);
|
||||
}
|
||||
|
||||
WindowManager.LayoutParams wlp=new WindowManager.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT);
|
||||
wlp.type=WindowManager.LayoutParams.TYPE_APPLICATION;
|
||||
@@ -296,6 +357,11 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
if(fromUser){
|
||||
float p=progress/10000f;
|
||||
updateVideoTimeText(Math.round(p*videoDuration));
|
||||
|
||||
// This moves the time view in sync with the seekbar thumb, but also makes sure it doesn't go off screen
|
||||
// (there must be at least 16dp between the time and the edge of the screen)
|
||||
float timeX=p*(seekBar.getWidth()-V.dp(32))+V.dp(16)-videoTimeView.getWidth()/2f;
|
||||
videoTimeView.setTranslationX(Math.max(-(videoTimeView.getLeft()-V.dp(16)), Math.min(timeX, videoControls.getWidth()-V.dp(16)-videoTimeView.getWidth()-videoTimeView.getLeft())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,6 +371,14 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
if(!uiVisible) // If dragging started during hide animation
|
||||
toggleUI();
|
||||
windowView.removeCallbacks(uiAutoHider);
|
||||
V.setVisibilityAnimated(videoTimeView, View.VISIBLE);
|
||||
postActions.animate().alpha(0f).setDuration(300).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
||||
altText.animate().alpha(0f).setDuration(300).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
||||
if(altText.getVisibility()==View.VISIBLE){
|
||||
videoTimeView.setTranslationY(seekBar.getHeight()+V.dp(12));
|
||||
}else{
|
||||
videoTimeView.setTranslationY(-videoTimeView.getHeight()-V.dp(12));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -312,15 +386,24 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
MediaPlayer player=findCurrentVideoPlayer();
|
||||
if(player!=null){
|
||||
float progress=seekBar.getProgress()/10000f;
|
||||
player.seekTo(Math.round(progress*player.getDuration()));
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O)
|
||||
player.seekTo(Math.round(progress*player.getDuration()), MediaPlayer.SEEK_CLOSEST);
|
||||
else
|
||||
player.seekTo(Math.round(progress*player.getDuration()));
|
||||
}
|
||||
hideUiDelayed();
|
||||
V.setVisibilityAnimated(videoTimeView, View.INVISIBLE);
|
||||
postActions.animate().alpha(1f).setDuration(300).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
||||
altText.animate().alpha(1f).setDuration(300).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
||||
}
|
||||
});
|
||||
videoSeekBar.setThumb(new VideoPlayerSeekBarThumbDrawable());
|
||||
|
||||
E.register(this);
|
||||
}
|
||||
|
||||
public void removeMenu(){
|
||||
toolbar.getMenu().clear();
|
||||
downloadButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -371,7 +454,7 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
.alpha(0)
|
||||
.setDuration(300)
|
||||
.setInterpolator(CubicBezierInterpolator.DEFAULT)
|
||||
.withEndAction(()->wm.removeView(windowView))
|
||||
.withEndAction(this::onDismissed)
|
||||
.start();
|
||||
}
|
||||
}
|
||||
@@ -399,6 +482,7 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
if(receiverRegistered){
|
||||
activity.unregisterReceiver(downloadCompletedReceiver);
|
||||
}
|
||||
E.unregister(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -407,21 +491,45 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
}
|
||||
|
||||
private void toggleUI(){
|
||||
if(currentUiVisibilityAnimation!=null)
|
||||
currentUiVisibilityAnimation.cancel();
|
||||
if(uiVisible){
|
||||
uiOverlay.animate()
|
||||
.alpha(0f)
|
||||
.setDuration(250)
|
||||
.setInterpolator(CubicBezierInterpolator.DEFAULT)
|
||||
.withEndAction(()->uiOverlay.setVisibility(View.GONE))
|
||||
.start();
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
set.playTogether(
|
||||
ObjectAnimator.ofFloat(uiOverlay, View.ALPHA, 0f),
|
||||
ObjectAnimator.ofFloat(toolbarWrap, View.TRANSLATION_Y, V.dp(-32)),
|
||||
ObjectAnimator.ofFloat(bottomBar, View.TRANSLATION_Y, V.dp(32))
|
||||
);
|
||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
set.setDuration(250);
|
||||
set.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
uiOverlay.setVisibility(View.GONE);
|
||||
currentUiVisibilityAnimation=null;
|
||||
}
|
||||
});
|
||||
currentUiVisibilityAnimation=set;
|
||||
set.start();
|
||||
windowView.setSystemUiVisibility(windowView.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_FULLSCREEN);
|
||||
}else{
|
||||
uiOverlay.setVisibility(View.VISIBLE);
|
||||
uiOverlay.animate()
|
||||
.alpha(1f)
|
||||
.setDuration(300)
|
||||
.setInterpolator(CubicBezierInterpolator.DEFAULT)
|
||||
.start();
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
set.playTogether(
|
||||
ObjectAnimator.ofFloat(uiOverlay, View.ALPHA, 1f),
|
||||
ObjectAnimator.ofFloat(toolbarWrap, View.TRANSLATION_Y, 0),
|
||||
ObjectAnimator.ofFloat(bottomBar, View.TRANSLATION_Y, 0)
|
||||
);
|
||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
set.setDuration(300);
|
||||
set.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
currentUiVisibilityAnimation=null;
|
||||
}
|
||||
});
|
||||
currentUiVisibilityAnimation=set;
|
||||
set.start();
|
||||
windowView.setSystemUiVisibility(windowView.getSystemUiVisibility() & ~(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_FULLSCREEN));
|
||||
if(attachments.get(currentIndex).type==Attachment.Type.VIDEO)
|
||||
hideUiDelayed(5000);
|
||||
@@ -448,6 +556,105 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
videoLastTimeUpdatePosition=-1;
|
||||
updateVideoTimeText(0);
|
||||
}
|
||||
updateAltText();
|
||||
}
|
||||
|
||||
private void updateAltText(){
|
||||
Attachment att=attachments.get(currentIndex);
|
||||
if(TextUtils.isEmpty(att.description)){
|
||||
altText.setVisibility(View.GONE);
|
||||
}else{
|
||||
altText.setVisibility(View.VISIBLE);
|
||||
altText.setText(att.description);
|
||||
altText.setMaxLines(att.type==Attachment.Type.VIDEO ? 3 : 4);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBackgroundColor(int position, float positionOffset){
|
||||
int color;
|
||||
if(positionOffset==0){
|
||||
color=backgroundColors[position];
|
||||
}else{
|
||||
color=UiUtils.alphaBlendColors(backgroundColors[position], backgroundColors[position+1], positionOffset);
|
||||
}
|
||||
int alpha=background.getAlpha();
|
||||
background.setColor(color);
|
||||
background.setAlpha(alpha);
|
||||
uiOverlay.setStatusBarColor(color & 0xe6ffffff);
|
||||
uiOverlay.setNavigationBarColor(color & 0xe6ffffff);
|
||||
bottomBar.setBackgroundTintList(ColorStateList.valueOf(color));
|
||||
}
|
||||
|
||||
private void updatePostActions(){
|
||||
bindActionButton(replyText, status.repliesCount);
|
||||
bindActionButton(boostText, status.reblogsCount);
|
||||
bindActionButton(favoriteText, status.favouritesCount);
|
||||
boostBtn.setSelected(status.reblogged);
|
||||
favoriteBtn.setSelected(status.favourited);
|
||||
bookmarkBtn.setSelected(status.bookmarked);
|
||||
bookmarkBtn.setContentDescription(activity.getString(status.bookmarked ? R.string.remove_bookmark : R.string.add_bookmark));
|
||||
boolean isOwn=status.account.id.equals(AccountSessionManager.getInstance().getAccount(accountID).self.id);
|
||||
boostBtn.setEnabled(status.visibility==StatusPrivacy.PUBLIC || status.visibility==StatusPrivacy.UNLISTED
|
||||
|| (status.visibility==StatusPrivacy.PRIVATE && isOwn));
|
||||
boostBtn.setAlpha(boostBtn.isEnabled() ? 1 : 0.5f);
|
||||
Drawable d=activity.getResources().getDrawable(switch(status.visibility){
|
||||
case PUBLIC, UNLISTED -> R.drawable.ic_boost;
|
||||
case PRIVATE -> isOwn ? R.drawable.ic_boost_private : R.drawable.ic_boost_disabled_24px;
|
||||
case DIRECT -> R.drawable.ic_boost_disabled_24px;
|
||||
}, activity.getTheme());
|
||||
d.setBounds(0, 0, V.dp(20), V.dp(20));
|
||||
boostText.setCompoundDrawablesRelative(d, null, null, null);
|
||||
}
|
||||
|
||||
private void bindActionButton(TextView btn, long count){
|
||||
if(count>0){
|
||||
btn.setText(UiUtils.abbreviateNumber(count));
|
||||
btn.setCompoundDrawablePadding(V.dp(6));
|
||||
}else{
|
||||
btn.setText("");
|
||||
btn.setCompoundDrawablePadding(0);
|
||||
}
|
||||
}
|
||||
|
||||
private void onPostActionClick(View view){
|
||||
int id=view.getId();
|
||||
if(id==R.id.boost_btn){
|
||||
if(status!=null){
|
||||
AccountSessionManager.get(accountID).getStatusInteractionController().setReblogged(status, !status.reblogged);
|
||||
}
|
||||
}else if(id==R.id.favorite_btn){
|
||||
if(status!=null){
|
||||
AccountSessionManager.get(accountID).getStatusInteractionController().setFavorited(status, !status.favourited);
|
||||
}
|
||||
}else if(id==R.id.share_btn){
|
||||
if(status!=null){
|
||||
UiUtils.openSystemShareSheet(activity, status);
|
||||
}
|
||||
}else if(id==R.id.bookmark_btn){
|
||||
if(status!=null){
|
||||
AccountSessionManager.get(accountID).getStatusInteractionController().setBookmarked(status, !status.bookmarked);
|
||||
}
|
||||
}else if(id==R.id.reply_btn){
|
||||
parentFragment.maybeShowPreReplySheet(status, ()->{
|
||||
onDismissed();
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("replyTo", Parcels.wrap(status));
|
||||
Nav.go(activity, ComposeFragment.class, args);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onStatusCountersUpdated(StatusCountersUpdatedEvent ev){
|
||||
if(status!=null && ev.id.equals(status.id)){
|
||||
status.reblogsCount=ev.reblogs;
|
||||
status.favouritesCount=ev.favorites;
|
||||
status.reblogged=ev.reblogged;
|
||||
status.favourited=ev.favorited;
|
||||
status.bookmarked=ev.bookmarked;
|
||||
updatePostActions();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -690,91 +897,12 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
}
|
||||
}
|
||||
|
||||
private void showInfoSheet(){
|
||||
private void showAltTextSheet(){
|
||||
pauseVideo();
|
||||
PhotoViewerInfoSheet sheet=new PhotoViewerInfoSheet(new ContextThemeWrapper(activity, R.style.Theme_Mastodon_Dark), attachments.get(currentIndex), toolbar.getHeight(), new PhotoViewerInfoSheet.Listener(){
|
||||
private boolean ignoreBeforeDismiss;
|
||||
|
||||
@Override
|
||||
public void onBeforeDismiss(int duration){
|
||||
if(ignoreBeforeDismiss)
|
||||
return;
|
||||
if(currentSheetRelatedToolbarAnimation!=null)
|
||||
currentSheetRelatedToolbarAnimation.cancel();
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
set.playTogether(
|
||||
ObjectAnimator.ofFloat(pager, View.TRANSLATION_Y, 0),
|
||||
ObjectAnimator.ofFloat(toolbarWrap, View.ALPHA, 1f),
|
||||
ObjectAnimator.ofArgb(uiOverlay, STATUS_BAR_COLOR_PROPERTY, 0x80000000)
|
||||
);
|
||||
set.setDuration(duration);
|
||||
set.setInterpolator(CubicBezierInterpolator.EASE_OUT);
|
||||
currentSheetRelatedToolbarAnimation=set;
|
||||
set.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
currentSheetRelatedToolbarAnimation=null;
|
||||
}
|
||||
});
|
||||
set.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismissEntireViewer(){
|
||||
ignoreBeforeDismiss=true;
|
||||
onStartSwipeToDismissTransition(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onButtonClick(int id){
|
||||
if(id==R.id.btn_boost){
|
||||
if(status!=null){
|
||||
AccountSessionManager.get(accountID).getStatusInteractionController().setReblogged(status, !status.reblogged);
|
||||
}
|
||||
}else if(id==R.id.btn_favorite){
|
||||
if(status!=null){
|
||||
AccountSessionManager.get(accountID).getStatusInteractionController().setFavorited(status, !status.favourited);
|
||||
}
|
||||
}else if(id==R.id.btn_share){
|
||||
if(status!=null){
|
||||
UiUtils.openSystemShareSheet(activity, status);
|
||||
}
|
||||
}else if(id==R.id.btn_bookmark){
|
||||
if(status!=null){
|
||||
AccountSessionManager.get(accountID).getStatusInteractionController().setBookmarked(status, !status.bookmarked);
|
||||
}
|
||||
}else if(id==R.id.btn_download){
|
||||
saveCurrentFile();
|
||||
}
|
||||
}
|
||||
});
|
||||
sheet.setStatus(status);
|
||||
BottomSheet sheet=new AltTextSheet(new ContextThemeWrapper(activity, UiUtils.getThemeForUserPreference(activity, GlobalUserPreferences.ThemePreference.DARK)),
|
||||
attachments.get(currentIndex));
|
||||
sheet.show();
|
||||
if(currentSheetRelatedToolbarAnimation!=null)
|
||||
currentSheetRelatedToolbarAnimation.cancel();
|
||||
sheet.getWindow().getDecorView().getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||
@Override
|
||||
public boolean onPreDraw(){
|
||||
sheet.getWindow().getDecorView().getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
set.playTogether(
|
||||
ObjectAnimator.ofFloat(pager, View.TRANSLATION_Y, -pager.getHeight()*0.2f),
|
||||
ObjectAnimator.ofFloat(toolbarWrap, View.ALPHA, 0f),
|
||||
ObjectAnimator.ofArgb(uiOverlay, STATUS_BAR_COLOR_PROPERTY, 0)
|
||||
);
|
||||
set.setDuration(300);
|
||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
currentSheetRelatedToolbarAnimation=set;
|
||||
set.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
currentSheetRelatedToolbarAnimation=null;
|
||||
}
|
||||
});
|
||||
set.start();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
sheet.getWindow().getDecorView().setSystemUiVisibility(sheet.getWindow().getDecorView().getSystemUiVisibility() & ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
|
||||
}
|
||||
|
||||
public interface Listener{
|
||||
|
||||
@@ -1,182 +0,0 @@
|
||||
package org.joinmastodon.android.ui.photoviewer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.BottomSheet;
|
||||
|
||||
public class PhotoViewerInfoSheet extends BottomSheet{
|
||||
private final Attachment attachment;
|
||||
private final View buttonsContainer;
|
||||
private final TextView altText;
|
||||
private final ImageButton backButton, infoButton;
|
||||
private final Button boostBtn, favoriteBtn, bookmarkBtn;
|
||||
private final Listener listener;
|
||||
private String statusID;
|
||||
|
||||
public PhotoViewerInfoSheet(@NonNull Context context, Attachment attachment, int toolbarHeight, Listener listener){
|
||||
super(context);
|
||||
this.attachment=attachment;
|
||||
this.listener=listener;
|
||||
|
||||
dimAmount=0;
|
||||
View content=context.getSystemService(LayoutInflater.class).inflate(R.layout.sheet_photo_viewer_info, null);
|
||||
setContentView(content);
|
||||
setNavigationBarBackground(new ColorDrawable(UiUtils.alphaBlendColors(UiUtils.getThemeColor(context, R.attr.colorM3Surface),
|
||||
UiUtils.getThemeColor(context, R.attr.colorM3Primary), 0.05f)), !UiUtils.isDarkTheme());
|
||||
|
||||
buttonsContainer=findViewById(R.id.buttons_container);
|
||||
altText=findViewById(R.id.alt_text);
|
||||
|
||||
if(TextUtils.isEmpty(attachment.description)){
|
||||
findViewById(R.id.alt_text).setVisibility(View.GONE);
|
||||
findViewById(R.id.alt_text_title).setVisibility(View.GONE);
|
||||
findViewById(R.id.divider).setVisibility(View.GONE);
|
||||
}else{
|
||||
altText.setText(attachment.description);
|
||||
findViewById(R.id.alt_text_help).setOnClickListener(v->showAltTextHelp());
|
||||
}
|
||||
|
||||
backButton=new ImageButton(context);
|
||||
backButton.setImageResource(me.grishka.appkit.R.drawable.ic_arrow_back);
|
||||
backButton.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(context, R.attr.colorM3OnSurfaceVariant)));
|
||||
backButton.setBackgroundResource(R.drawable.bg_button_m3_tonal_icon);
|
||||
backButton.setOutlineProvider(ViewOutlineProvider.BACKGROUND);
|
||||
backButton.setElevation(V.dp(2));
|
||||
backButton.setAlpha(0f);
|
||||
backButton.setContentDescription(context.getString(R.string.back));
|
||||
backButton.setOnClickListener(v->{
|
||||
listener.onDismissEntireViewer();
|
||||
dismiss();
|
||||
});
|
||||
|
||||
infoButton=new ImageButton(context);
|
||||
infoButton.setImageResource(R.drawable.ic_info_fill1_24px);
|
||||
infoButton.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(context, R.attr.colorM3OnPrimary)));
|
||||
infoButton.setBackgroundResource(R.drawable.bg_button_m3_filled_icon);
|
||||
infoButton.setOutlineProvider(ViewOutlineProvider.BACKGROUND);
|
||||
infoButton.setElevation(V.dp(2));
|
||||
infoButton.setAlpha(0f);
|
||||
infoButton.setSelected(true);
|
||||
infoButton.setContentDescription(context.getString(R.string.info));
|
||||
infoButton.setOnClickListener(v->dismiss());
|
||||
|
||||
FrameLayout.LayoutParams lp=new FrameLayout.LayoutParams(V.dp(48), V.dp(48));
|
||||
lp.topMargin=toolbarHeight/2-V.dp(24);
|
||||
lp.leftMargin=lp.rightMargin=V.dp(4);
|
||||
lp.gravity=Gravity.START | Gravity.TOP;
|
||||
container.addView(backButton, lp);
|
||||
|
||||
lp=new FrameLayout.LayoutParams(lp);
|
||||
lp.leftMargin=lp.rightMargin=0;
|
||||
lp.gravity=Gravity.END | Gravity.TOP;
|
||||
container.addView(infoButton, lp);
|
||||
|
||||
boostBtn=findViewById(R.id.btn_boost);
|
||||
favoriteBtn=findViewById(R.id.btn_favorite);
|
||||
bookmarkBtn=findViewById(R.id.btn_bookmark);
|
||||
View.OnClickListener clickListener=v->listener.onButtonClick(v.getId());
|
||||
|
||||
boostBtn.setOnClickListener(clickListener);
|
||||
favoriteBtn.setOnClickListener(clickListener);
|
||||
findViewById(R.id.btn_share).setOnClickListener(clickListener);
|
||||
bookmarkBtn.setOnClickListener(clickListener);
|
||||
findViewById(R.id.btn_download).setOnClickListener(clickListener);
|
||||
}
|
||||
|
||||
private void showAltTextHelp(){
|
||||
new M3AlertDialogBuilder(getContext())
|
||||
.setTitle(R.string.what_is_alt_text)
|
||||
.setMessage(UiUtils.fixBulletListInString(getContext(), R.string.alt_text_help))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dismiss(){
|
||||
if(dismissed)
|
||||
return;
|
||||
int height=content.getHeight();
|
||||
int duration=Math.max(60, (int) (180 * (height - content.getTranslationY()) / (float) height));
|
||||
listener.onBeforeDismiss(duration);
|
||||
backButton.animate().alpha(0).setDuration(duration).setInterpolator(CubicBezierInterpolator.EASE_OUT).start();
|
||||
infoButton.animate().alpha(0).setDuration(duration).setInterpolator(CubicBezierInterpolator.EASE_OUT).start();
|
||||
super.dismiss();
|
||||
E.unregister(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show(){
|
||||
super.show();
|
||||
E.register(this);
|
||||
content.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||
@Override
|
||||
public boolean onPreDraw(){
|
||||
content.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
backButton.animate().alpha(1).setDuration(300).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
||||
infoButton.animate().alpha(1).setDuration(300).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setStatus(Status status){
|
||||
statusID=status.id;
|
||||
boostBtn.setCompoundDrawablesWithIntrinsicBounds(0, switch(status.visibility){
|
||||
case DIRECT -> R.drawable.ic_boost_disabled_24px;
|
||||
case PUBLIC, UNLISTED -> R.drawable.ic_boost;
|
||||
case PRIVATE -> R.drawable.ic_boost_private;
|
||||
}, 0, 0);
|
||||
boostBtn.setEnabled(status.visibility!=StatusPrivacy.DIRECT);
|
||||
setButtonStates(status.reblogged, status.favourited, status.bookmarked);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onCountersUpdated(StatusCountersUpdatedEvent ev){
|
||||
if(ev.id.equals(statusID)){
|
||||
setButtonStates(ev.reblogged, ev.favorited, ev.bookmarked);
|
||||
}
|
||||
}
|
||||
|
||||
private void setButtonStates(boolean reblogged, boolean favorited, boolean bookmarked){
|
||||
boostBtn.setText(reblogged ? R.string.button_reblogged : R.string.button_reblog);
|
||||
boostBtn.setSelected(reblogged);
|
||||
|
||||
favoriteBtn.setText(favorited ? R.string.button_favorited : R.string.button_favorite);
|
||||
favoriteBtn.setSelected(favorited);
|
||||
|
||||
bookmarkBtn.setText(bookmarked ? R.string.bookmarked : R.string.add_bookmark);
|
||||
bookmarkBtn.setSelected(bookmarked);
|
||||
}
|
||||
|
||||
public interface Listener{
|
||||
void onBeforeDismiss(int duration);
|
||||
void onDismissEntireViewer();
|
||||
void onButtonClick(int id);
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,8 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
|
||||
private float lastScaleCenterX, lastScaleCenterY;
|
||||
private boolean canScrollLeft, canScrollRight;
|
||||
private ArrayList<SpringAnimation> runningTransformAnimations=new ArrayList<>(), runningTransitionAnimations=new ArrayList<>();
|
||||
private boolean fill; // whether the image should fill the viewport at min scale
|
||||
private boolean swipeToDismissEnabled=true;
|
||||
|
||||
private RectF tmpRect=new RectF(), tmpRect2=new RectF();
|
||||
// the initial/final crop rect for open/close transitions, in child coordinates
|
||||
@@ -116,14 +118,19 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
|
||||
if(child==null)
|
||||
return;
|
||||
|
||||
int width=right-left;
|
||||
int height=bottom-top;
|
||||
int width=right-left-getPaddingLeft()-getPaddingRight();
|
||||
int height=bottom-top-getPaddingTop()-getPaddingBottom();
|
||||
if(width==0 || height==0 || child.getWidth()==0 || child.getWidth()==0){
|
||||
matrix.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
float scale=Math.min(width/(float)child.getWidth(), height/(float)child.getHeight());
|
||||
float scale;
|
||||
if(fill){
|
||||
scale=Math.max(width/(float)child.getWidth(), height/(float)child.getHeight());
|
||||
}else{
|
||||
scale=Math.min(width/(float)child.getWidth(), height/(float)child.getHeight());
|
||||
}
|
||||
minScale=scale;
|
||||
maxScale=Math.max(3f, height/(float)child.getHeight());
|
||||
matrix.setScale(scale, scale);
|
||||
@@ -323,14 +330,14 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
|
||||
private void updateLimits(float targetScale){
|
||||
float scaledWidth=child.getWidth()*targetScale;
|
||||
float scaledHeight=child.getHeight()*targetScale;
|
||||
if(scaledWidth>getWidth()){
|
||||
minTransX=(getWidth()-Math.round(scaledWidth))/2f;
|
||||
if(scaledWidth>getInsetWidth()){
|
||||
minTransX=(getInsetWidth()-Math.round(scaledWidth))/2f;
|
||||
maxTransX=-minTransX;
|
||||
}else{
|
||||
minTransX=maxTransX=0f;
|
||||
}
|
||||
if(scaledHeight>getHeight()){
|
||||
minTransY=(getHeight()-Math.round(scaledHeight))/2f;
|
||||
if(scaledHeight>getInsetHeight()){
|
||||
minTransY=(getInsetHeight()-Math.round(scaledHeight))/2f;
|
||||
maxTransY=-minTransY;
|
||||
}else{
|
||||
minTransY=maxTransY=0f;
|
||||
@@ -468,10 +475,10 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
|
||||
@Override
|
||||
public boolean onScale(ScaleGestureDetector detector){
|
||||
float factor=detector.getScaleFactor();
|
||||
matrix.postScale(factor, factor, detector.getFocusX()-getWidth()/2f, detector.getFocusY()-getHeight()/2f);
|
||||
matrix.postScale(factor, factor, detector.getFocusX()-getInsetWidth()/2f-getPaddingLeft(), detector.getFocusY()-getInsetHeight()/2f-getPaddingTop());
|
||||
updateViewTransform(false);
|
||||
lastScaleCenterX=detector.getFocusX()-getWidth()/2f;
|
||||
lastScaleCenterY=detector.getFocusY()-getHeight()/2f;
|
||||
lastScaleCenterX=detector.getFocusX()-getInsetWidth()/2f-getPaddingLeft();
|
||||
lastScaleCenterY=detector.getFocusY()-getInsetHeight()/2f-getPaddingTop();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -510,7 +517,7 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
|
||||
return false;
|
||||
if(child.getScaleX()<maxScale){
|
||||
float scale=maxScale/child.getScaleX();
|
||||
matrix.postScale(scale, scale, e.getX()-getWidth()/2f, e.getY()-getHeight()/2f);
|
||||
matrix.postScale(scale, scale, e.getX()-getInsetWidth()/2f-getPaddingLeft(), e.getY()-getInsetHeight()/2f-getPaddingTop());
|
||||
matrix.getValues(matrixValues);
|
||||
transX=matrixValues[Matrix.MTRANS_X];
|
||||
transY=matrixValues[Matrix.MTRANS_Y];
|
||||
@@ -554,7 +561,7 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY){
|
||||
if(minTransY==maxTransY && minTransY==0f){
|
||||
if(minTransX==maxTransX && minTransX==0f){
|
||||
if(Math.abs(totalScrollY)>Math.abs(totalScrollX)){
|
||||
if(Math.abs(totalScrollY)>Math.abs(totalScrollX) && swipeToDismissEnabled){
|
||||
if(!swipingToDismiss){
|
||||
swipingToDismiss=true;
|
||||
matrix.postTranslate(-totalScrollX, 0);
|
||||
@@ -630,6 +637,38 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
|
||||
}
|
||||
}
|
||||
|
||||
public int getInsetWidth(){
|
||||
return getWidth()-getPaddingLeft()-getPaddingRight();
|
||||
}
|
||||
|
||||
public int getInsetHeight(){
|
||||
return getHeight()-getPaddingTop()-getPaddingBottom();
|
||||
}
|
||||
|
||||
public void setFill(boolean fill){
|
||||
this.fill=fill;
|
||||
}
|
||||
|
||||
public void endAllAnimations(){
|
||||
if(!runningTransformAnimations.isEmpty()){
|
||||
endTransformAnimations();
|
||||
}else{
|
||||
springBack();
|
||||
endTransformAnimations();
|
||||
}
|
||||
updateViewTransform(false);
|
||||
}
|
||||
|
||||
public void setSwipeToDismissEnabled(boolean swipeToDismissEnabled){
|
||||
this.swipeToDismissEnabled=swipeToDismissEnabled;
|
||||
}
|
||||
|
||||
private void endTransformAnimations(){
|
||||
for(SpringAnimation anim:new ArrayList<>(runningTransformAnimations)){
|
||||
anim.skipToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
public interface Listener{
|
||||
void onTransitionAnimationUpdate(float translateX, float translateY, float scale);
|
||||
void onTransitionAnimationFinished();
|
||||
|
||||
@@ -72,6 +72,7 @@ public class AccountSwitcherSheet extends BottomSheet{
|
||||
imgLoader=new ListImageLoaderWrapper(activity, list, list, null);
|
||||
list.setClipToPadding(false);
|
||||
list.setLayoutManager(new LinearLayoutManager(activity));
|
||||
list.setOverScrollMode(View.OVER_SCROLL_IF_CONTENT_SCROLLS);
|
||||
|
||||
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
|
||||
View handle=new View(activity);
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.text.TextPaint;
|
||||
import android.text.style.TypefaceSpan;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.ui.ColorContrastMode;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -21,7 +22,11 @@ public abstract class BaseMonospaceSpan extends TypefaceSpan{
|
||||
@Override
|
||||
public void updateDrawState(@NonNull TextPaint paint){
|
||||
super.updateDrawState(paint);
|
||||
paint.setColor(UiUtils.getThemeColor(context, R.attr.colorRichTextText));
|
||||
if(!UiUtils.isDarkTheme() && UiUtils.getColorContrastMode(context)==ColorContrastMode.HIGH){
|
||||
|
||||
}else{
|
||||
paint.setColor(UiUtils.getThemeColor(context, R.attr.colorRichTextText));
|
||||
}
|
||||
paint.setTextSize(paint.getTextSize()*0.9375f);
|
||||
paint.baselineShift=V.dp(-1);
|
||||
}
|
||||
|
||||
@@ -41,16 +41,7 @@ public class BlockQuoteSpan extends CharacterStyle implements LeadingMarginSpan{
|
||||
@Override
|
||||
public void drawLeadingMargin(@NonNull Canvas c, @NonNull Paint p, int x, int dir, int top, int baseline, int bottom, @NonNull CharSequence text, int start, int end, boolean first, @NonNull Layout layout){
|
||||
if(text instanceof Spanned s && s.getSpanStart(this)==start){
|
||||
int color;
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.S && UiUtils.isDarkTheme()){
|
||||
color=UiUtils.alphaBlendColors(
|
||||
context.getColor(android.R.color.system_accent3_700),
|
||||
context.getColor(android.R.color.system_accent3_800),
|
||||
0.5f
|
||||
);
|
||||
}else{
|
||||
color=UiUtils.getThemeColor(context, R.attr.colorRichTextDecorations);
|
||||
}
|
||||
int color=UiUtils.getThemeColor(context, R.attr.colorRichTextDecorations);
|
||||
int level=s.getSpans(start, end, LeadingMarginSpan.class).length-1;
|
||||
if(dir<0){ // RTL
|
||||
if(level==0){
|
||||
|
||||
@@ -8,8 +8,6 @@ import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.BackgroundColorSpan;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.LineHeightSpan;
|
||||
import android.text.style.RelativeSizeSpan;
|
||||
import android.text.style.StrikethroughSpan;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.widget.TextView;
|
||||
@@ -95,9 +93,11 @@ public class HtmlParser{
|
||||
Map<String, Hashtag> tagsByTag=tags.stream().distinct().collect(Collectors.toMap(t->t.name.toLowerCase(), Function.identity()));
|
||||
Map<String, Mention> mentionsByID=mentions.stream().distinct().collect(Collectors.toMap(m->m.id, Function.identity()));
|
||||
|
||||
source=source.replaceAll("[\u2028\u2029]", "<br>");
|
||||
final SpannableStringBuilder ssb=new SpannableStringBuilder();
|
||||
Jsoup.parseBodyFragment(source).body().traverse(new NodeVisitor(){
|
||||
private final ArrayList<SpanInfo> openSpans=new ArrayList<>();
|
||||
private boolean lastElementWasBlock=false;
|
||||
|
||||
private boolean isInsidePre(){
|
||||
for(SpanInfo si:openSpans){
|
||||
@@ -119,6 +119,13 @@ public class HtmlParser{
|
||||
@Override
|
||||
public void head(@NonNull Node node, int depth){
|
||||
if(node instanceof TextNode textNode){
|
||||
if(lastElementWasBlock){
|
||||
lastElementWasBlock=false;
|
||||
if(!textNode.text().trim().isEmpty()){
|
||||
ssb.append('\n');
|
||||
ssb.append("\n", new SpacerSpan(1, V.dp(8)), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
if(isInsidePre()){
|
||||
ssb.append(textNode.getWholeText().stripTrailing());
|
||||
}else{
|
||||
@@ -128,6 +135,11 @@ public class HtmlParser{
|
||||
ssb.append(text);
|
||||
}
|
||||
}else if(node instanceof Element el){
|
||||
if(lastElementWasBlock || (el.isBlock() && !"li".equals(el.nodeName()) && !"ul".equals(el.nodeName()) && !"ol".equals(el.nodeName()) && ssb.length()>0 && ssb.charAt(ssb.length()-1)!='\n')){
|
||||
lastElementWasBlock=false;
|
||||
ssb.append('\n');
|
||||
ssb.append("\n", new SpacerSpan(1, V.dp(8)), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
switch(el.nodeName()){
|
||||
case "a" -> {
|
||||
Object linkObject=null;
|
||||
@@ -208,16 +220,9 @@ public class HtmlParser{
|
||||
public void tail(@NonNull Node node, int depth){
|
||||
if(node instanceof Element el){
|
||||
String name=el.nodeName();
|
||||
lastElementWasBlock|=el.isBlock();
|
||||
if("span".equals(name) && el.hasClass("ellipsis")){
|
||||
ssb.append("…", new DeleteWhenCopiedSpan(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}else if("p".equals(name) || "ol".equals(name) || "ul".equals(name)){
|
||||
if(node.nextSibling()!=null && "body".equals(node.parent().nodeName())){
|
||||
ssb.append('\n');
|
||||
ssb.append("\n", new SpacerSpan(1, V.dp(8)), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}else if("pre".equals(name)){
|
||||
if(node.nextSibling()!=null)
|
||||
ssb.append("\n");
|
||||
}
|
||||
if(!openSpans.isEmpty()){
|
||||
SpanInfo si=openSpans.get(openSpans.size()-1);
|
||||
@@ -234,6 +239,12 @@ public class HtmlParser{
|
||||
}
|
||||
}
|
||||
});
|
||||
int trailingTrimLength=0;
|
||||
for(int i=ssb.length()-1;i>=0 && Character.isWhitespace(ssb.charAt(i));i--){
|
||||
trailingTrimLength++;
|
||||
}
|
||||
if(trailingTrimLength>0)
|
||||
ssb.replace(ssb.length()-trailingTrimLength, ssb.length(), "");
|
||||
if(!emojis.isEmpty())
|
||||
parseCustomEmoji(ssb, emojis);
|
||||
return ssb;
|
||||
|
||||
@@ -58,6 +58,12 @@ public class BlurHashDecoder{
|
||||
return composeBitmap(width, height, numCompX, numCompY, colors, useCache);
|
||||
}
|
||||
|
||||
public static int decodeToSingleColor(String hash){
|
||||
if(hash.length()<6)
|
||||
return 0;
|
||||
return decode83(hash, 2, 6) & 0xFFFFFF;
|
||||
}
|
||||
|
||||
private static int decode83(String str, int from, int to){
|
||||
int result=0;
|
||||
for(int i=from;i<to;i++){
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.joinmastodon.android.ui.utils;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.UiModeManager;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ClipData;
|
||||
import android.content.ComponentName;
|
||||
@@ -24,6 +26,8 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.os.ext.SdkExtensions;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.OpenableColumns;
|
||||
@@ -75,6 +79,7 @@ import org.joinmastodon.android.model.Hashtag;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.model.SearchResults;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.ColorContrastMode;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.Snackbar;
|
||||
import org.joinmastodon.android.ui.sheets.BlockAccountConfirmationSheet;
|
||||
@@ -720,11 +725,47 @@ public class UiUtils{
|
||||
}
|
||||
|
||||
public static void setUserPreferredTheme(Context context){
|
||||
context.setTheme(switch(GlobalUserPreferences.theme){
|
||||
case AUTO -> R.style.Theme_Mastodon_AutoLightDark;
|
||||
case LIGHT -> R.style.Theme_Mastodon_Light;
|
||||
case DARK -> R.style.Theme_Mastodon_Dark;
|
||||
});
|
||||
context.setTheme(getThemeForUserPreference(context, GlobalUserPreferences.theme));
|
||||
}
|
||||
|
||||
public static int getThemeForUserPreference(Context context, GlobalUserPreferences.ThemePreference pref){
|
||||
if(GlobalUserPreferences.useDynamicColors){
|
||||
return switch(pref){
|
||||
case AUTO -> switch(getColorContrastMode(context)){
|
||||
case DEFAULT -> R.style.Theme_Mastodon_AutoLightDark;
|
||||
case MEDIUM -> R.style.Theme_Mastodon_AutoLightDark_MediumContrast;
|
||||
case HIGH -> R.style.Theme_Mastodon_AutoLightDark_HighContrast;
|
||||
};
|
||||
case LIGHT -> switch(getColorContrastMode(context)){
|
||||
case DEFAULT -> R.style.Theme_Mastodon_Light;
|
||||
case MEDIUM -> R.style.Theme_Mastodon_Light_MediumContrast;
|
||||
case HIGH -> R.style.Theme_Mastodon_Light_HighContrast;
|
||||
};
|
||||
case DARK -> switch(getColorContrastMode(context)){
|
||||
case DEFAULT -> R.style.Theme_Mastodon_Dark;
|
||||
case MEDIUM -> R.style.Theme_Mastodon_Dark_MediumContrast;
|
||||
case HIGH -> R.style.Theme_Mastodon_Dark_HighContrast;
|
||||
};
|
||||
};
|
||||
}else{
|
||||
return switch(pref){
|
||||
case AUTO -> switch(getColorContrastMode(context)){
|
||||
case DEFAULT -> R.style.Theme_Mastodon_AutoLightDark_Masterial;
|
||||
case MEDIUM -> R.style.Theme_Mastodon_AutoLightDark_MediumContrast_Masterial;
|
||||
case HIGH -> R.style.Theme_Mastodon_AutoLightDark_HighContrast_Masterial;
|
||||
};
|
||||
case LIGHT -> switch(getColorContrastMode(context)){
|
||||
case DEFAULT -> R.style.Theme_Mastodon_Light_Masterial;
|
||||
case MEDIUM -> R.style.Theme_Mastodon_Light_MediumContrast_Masterial;
|
||||
case HIGH -> R.style.Theme_Mastodon_Light_HighContrast_Masterial;
|
||||
};
|
||||
case DARK -> switch(getColorContrastMode(context)){
|
||||
case DEFAULT -> R.style.Theme_Mastodon_Dark_Masterial;
|
||||
case MEDIUM -> R.style.Theme_Mastodon_Dark_MediumContrast_Masterial;
|
||||
case HIGH -> R.style.Theme_Mastodon_Dark_HighContrast_Masterial;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isDarkTheme(){
|
||||
@@ -1086,4 +1127,20 @@ public class UiUtils{
|
||||
rv.scrollBy(0, -topItemOffset);
|
||||
}
|
||||
}
|
||||
|
||||
public static ColorContrastMode getColorContrastMode(Context context){
|
||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
||||
return ColorContrastMode.DEFAULT;
|
||||
return ColorContrastMode.fromContrastValue(context.getSystemService(UiModeManager.class).getContrast());
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.R)
|
||||
public static boolean playVibrationEffectIfSupported(Context context, int effect){
|
||||
Vibrator vibrator=context.getSystemService(Vibrator.class);
|
||||
if(vibrator.areAllPrimitivesSupported(effect)){
|
||||
vibrator.vibrate(VibrationEffect.startComposition().addPrimitive(effect).compose());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,10 @@ import android.graphics.RectF;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Checkable;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
@@ -350,11 +348,7 @@ public class ComposePollViewController{
|
||||
pollOptionsView.removeView(view);
|
||||
addPollOptionBtn.setEnabled(pollOptions.size()<maxPollOptions);
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.R){
|
||||
Vibrator vibrator=fragment.getActivity().getSystemService(Vibrator.class);
|
||||
if(vibrator.areAllPrimitivesSupported(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)){
|
||||
VibrationEffect effect=VibrationEffect.startComposition().addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE).compose();
|
||||
vibrator.vibrate(effect);
|
||||
}
|
||||
UiUtils.playVibrationEffectIfSupported(fragment.getActivity(), VibrationEffect.Composition.PRIMITIVE_QUICK_RISE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -93,11 +93,9 @@ public class LinkCardHolder<T extends LinkCardHolder.LinkCardProvider> extends S
|
||||
authorChip.setVisibility(View.VISIBLE);
|
||||
authorBefore.setVisibility(View.VISIBLE);
|
||||
String[] authorParts=itemView.getContext().getString(R.string.article_by_author, "{author}").split("\\{author\\}");
|
||||
String before=authorParts[0].trim();
|
||||
String before=authorParts.length>0 ? authorParts[0].trim() : "";
|
||||
String after=authorParts.length>1 ? authorParts[1].trim() : "";
|
||||
if(!TextUtils.isEmpty(before)){
|
||||
authorBefore.setText(before);
|
||||
}
|
||||
authorBefore.setText(before);
|
||||
if(TextUtils.isEmpty(after)){
|
||||
authorAfter.setVisibility(View.GONE);
|
||||
}else{
|
||||
|
||||
@@ -24,6 +24,10 @@ public class CheckIconSelectableTextView extends TextView{
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
protected int getCheckmarkColorAttribute(){
|
||||
return R.attr.colorM3OnSurface;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawableStateChanged(){
|
||||
super.drawableStateChanged();
|
||||
@@ -32,7 +36,7 @@ public class CheckIconSelectableTextView extends TextView{
|
||||
currentlySelected=isSelected();
|
||||
Drawable start=currentlySelected ? getResources().getDrawable(R.drawable.ic_baseline_check_18, getContext().getTheme()).mutate() : null;
|
||||
if(start!=null)
|
||||
start.setTint(UiUtils.getThemeColor(getContext(), R.attr.colorM3OnSurface));
|
||||
start.setTintList(getTextColors());
|
||||
Drawable end=getCompoundDrawablesRelative()[2];
|
||||
setCompoundDrawablesRelativeWithIntrinsicBounds(start, null, end, null);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,10 @@ public class FilterChipView extends CheckIconSelectableTextView{
|
||||
updatePadding();
|
||||
}
|
||||
|
||||
protected int getCheckmarkColorAttribute(){
|
||||
return R.attr.colorM3OnSecondaryContainer;
|
||||
}
|
||||
|
||||
private void updatePadding(){
|
||||
int vertical=V.dp(6);
|
||||
Drawable[] drawables=getCompoundDrawablesRelative();
|
||||
|
||||
@@ -51,6 +51,7 @@ public class NewPostsButtonContainer extends FrameLayout{
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev){
|
||||
getParent().requestDisallowInterceptTouchEvent(true);
|
||||
if(gestureDetector.onTouchEvent(ev))
|
||||
return true;
|
||||
return super.onInterceptTouchEvent(ev);
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.Layout;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
|
||||
import me.grishka.appkit.utils.CustomViewHelper;
|
||||
|
||||
public class PhotoViewerAltTextView extends TextView implements CustomViewHelper{
|
||||
private String moreText;
|
||||
private Paint morePaint=new Paint(), clearPaint=new Paint();
|
||||
private Matrix matrix=new Matrix();
|
||||
private LinearGradient gradient, rtlGradient;
|
||||
|
||||
public PhotoViewerAltTextView(Context context){
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public PhotoViewerAltTextView(Context context, AttributeSet attrs){
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public PhotoViewerAltTextView(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
moreText=context.getString(R.string.text_show_more).toUpperCase();
|
||||
clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
|
||||
gradient=new LinearGradient(0, 0, dp(56), 0, 0x00ffffff, 0xffffffff, Shader.TileMode.CLAMP);
|
||||
rtlGradient=new LinearGradient(0, 0, dp(56), 0, 0xffffffff, 0x00ffffff, Shader.TileMode.CLAMP);
|
||||
setLayerType(LAYER_TYPE_HARDWARE, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas){
|
||||
super.onDraw(canvas);
|
||||
Layout layout=getLayout();
|
||||
if(layout.getLineCount()>=getMaxLines() && layout.getEllipsisCount(layout.getLineCount()-1)>0){
|
||||
int lastLine=layout.getLineCount()-1;
|
||||
morePaint.set(getPaint());
|
||||
morePaint.setTypeface(Typeface.DEFAULT_BOLD);
|
||||
float moreWidth=morePaint.measureText(moreText);
|
||||
int lineTop=layout.getLineTop(lastLine);
|
||||
int lineBottom=layout.getLineBottom(lastLine);
|
||||
int viewRight=getWidth()-getPaddingRight();
|
||||
int gradientWidth=dp(56);
|
||||
|
||||
if(layout.getParagraphDirection(lastLine)==Layout.DIR_RIGHT_TO_LEFT){
|
||||
matrix.setTranslate(getPaddingLeft()+moreWidth, lineTop);
|
||||
rtlGradient.setLocalMatrix(matrix);
|
||||
clearPaint.setShader(rtlGradient);
|
||||
canvas.drawRect(getPaddingLeft(), lineTop, getPaddingLeft()+moreWidth+gradientWidth, lineBottom, clearPaint);
|
||||
canvas.drawText(moreText, getPaddingLeft(), layout.getLineBaseline(lastLine), morePaint);
|
||||
}else{
|
||||
matrix.setTranslate(viewRight-moreWidth-gradientWidth, lineTop);
|
||||
gradient.setLocalMatrix(matrix);
|
||||
clearPaint.setShader(gradient);
|
||||
canvas.drawRect(viewRight-moreWidth-gradientWidth, lineTop, viewRight, lineBottom, clearPaint);
|
||||
canvas.drawText(moreText, viewRight-moreWidth, layout.getLineBaseline(lastLine), morePaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
public class WindowRootFrameLayout extends FrameLayout{
|
||||
private OnKeyListener dispatchKeyEventListener;
|
||||
private OnApplyWindowInsetsListener dispatchApplyWindowInsetsListener;
|
||||
|
||||
public WindowRootFrameLayout(Context context){
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public WindowRootFrameLayout(Context context, AttributeSet attrs){
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public WindowRootFrameLayout(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event){
|
||||
return (dispatchKeyEventListener!=null && dispatchKeyEventListener.onKey(this, event.getKeyCode(), event)) || super.dispatchKeyEvent(event);
|
||||
}
|
||||
|
||||
public void setDispatchKeyEventListener(OnKeyListener dispatchKeyEventListener){
|
||||
this.dispatchKeyEventListener=dispatchKeyEventListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets){
|
||||
if(dispatchApplyWindowInsetsListener!=null)
|
||||
return dispatchApplyWindowInsetsListener.onApplyWindowInsets(this, insets);
|
||||
return super.dispatchApplyWindowInsets(insets);
|
||||
}
|
||||
|
||||
public void setDispatchApplyWindowInsetsListener(OnApplyWindowInsetsListener dispatchApplyWindowInsetsListener){
|
||||
this.dispatchApplyWindowInsetsListener=dispatchApplyWindowInsetsListener;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?colorM3Surface" android:alpha="0.95"/>
|
||||
<item android:color="@android:color/system_accent3_700" android:lStar="25"/>
|
||||
</selector>
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_selected="true" android:drawable="@drawable/ic_check_20px"/>
|
||||
<item android:color="@android:color/system_accent3_800" android:lStar="15"/>
|
||||
</selector>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?colorM3PrimaryContainer" android:alpha="?composePollStyleInactiveAlpha"/>
|
||||
</selector>
|
||||
4
mastodon/src/main/res/color/m3_outline_overlay.xml
Normal file
4
mastodon/src/main/res/color/m3_outline_overlay.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?colorM3Outline" android:alpha="@dimen/overlay_ripple_alpha"/>
|
||||
</selector>
|
||||
@@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/bg_button_m3_filled" android:inset="4dp">
|
||||
</inset>
|
||||
@@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/bg_button_m3_tonal" android:inset="4dp">
|
||||
</inset>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:width="24dp" android:height="24dp" android:gravity="start|center_vertical" android:start="6dp">
|
||||
<ripple android:color="@color/m3_white_overlay"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:width="24dp" android:height="24dp" android:gravity="center">
|
||||
<ripple android:color="@color/m3_white_overlay"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<gradient
|
||||
android:type="linear"
|
||||
android:startColor="#00000000"
|
||||
android:endColor="#E6000000"
|
||||
android:angle="270"/>
|
||||
</shape>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/m3_white_overlay">
|
||||
<item android:gravity="center" android:width="32dp" android:height="32dp">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="#80000000"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/m3_on_surface_overlay">
|
||||
<item android:id="@android:id/mask">
|
||||
<shape>
|
||||
<solid android:color="#000"/>
|
||||
<corners android:radius="12dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/m3_on_surface_overlay">
|
||||
<item android:id="@android:id/mask">
|
||||
<shape>
|
||||
<solid android:color="#000"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
6
mastodon/src/main/res/drawable/bg_status_action.xml
Normal file
6
mastodon/src/main/res/drawable/bg_status_action.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:width="24dp" android:height="24dp" android:gravity="start|center_vertical" android:start="6dp">
|
||||
<ripple android:color="@color/m3_outline_overlay"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:width="24dp" android:height="24dp" android:gravity="center">
|
||||
<ripple android:color="@color/m3_outline_overlay"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:start="16dp">
|
||||
<shape>
|
||||
<solid android:color="?colorM3OutlineVariant"/>
|
||||
<size android:height="0.5dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -1,9 +0,0 @@
|
||||
<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="M11,20V7.825L5.4,13.425L4,12L12,4L20,12L18.6,13.425L13,7.825V20Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_bookmark_20px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_bookmark_20px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M240,816L240,216Q240,186.3 261.15,165.15Q282.3,144 312,144L648,144Q677.7,144 698.85,165.15Q720,186.3 720,216L720,816L480,720L240,816ZM312,709L480,642L648,709L648,216Q648,216 648,216Q648,216 648,216L312,216Q312,216 312,216Q312,216 312,216L312,709ZM312,216L312,216Q312,216 312,216Q312,216 312,216L648,216Q648,216 648,216Q648,216 648,216L648,216L480,216L312,216Z"/>
|
||||
</vector>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/ic_bookmark_fill1_24px" android:state_selected="true"/>
|
||||
<item android:drawable="@drawable/ic_bookmark_24px"/>
|
||||
<item android:drawable="@drawable/ic_bookmark_fill1_20px" android:state_selected="true"/>
|
||||
<item android:drawable="@drawable/ic_bookmark_20px"/>
|
||||
</selector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M240,816L240,216Q240,186 261,165Q282,144 312,144L648,144Q678,144 699,165Q720,186 720,216L720,816L480,720L240,816Z"/>
|
||||
</vector>
|
||||
@@ -1,9 +0,0 @@
|
||||
<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="M5,21V5Q5,4.175 5.588,3.587Q6.175,3 7,3H17Q17.825,3 18.413,3.587Q19,4.175 19,5V21L12,18Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_close_20px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_close_20px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M291,720L240,669L429,480L240,291L291,240L480,429L669,240L720,291L531,480L720,669L669,720L480,531L291,720Z"/>
|
||||
</vector>
|
||||
@@ -1,9 +0,0 @@
|
||||
<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="M6,20Q5.175,20 4.588,19.413Q4,18.825 4,18V15H6V18Q6,18 6,18Q6,18 6,18H18Q18,18 18,18Q18,18 18,18V15H20V18Q20,18.825 19.413,19.413Q18.825,20 18,20ZM12,16 L7,11 8.4,9.55 11,12.15V4H13V12.15L15.6,9.55L17,11Z"/>
|
||||
</vector>
|
||||
@@ -1,9 +0,0 @@
|
||||
<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="M11,17H13V11H11ZM12,9Q12.425,9 12.713,8.712Q13,8.425 13,8Q13,7.575 12.713,7.287Q12.425,7 12,7Q11.575,7 11.288,7.287Q11,7.575 11,8Q11,8.425 11.288,8.712Q11.575,9 12,9ZM12,22Q9.925,22 8.1,21.212Q6.275,20.425 4.925,19.075Q3.575,17.725 2.788,15.9Q2,14.075 2,12Q2,9.925 2.788,8.1Q3.575,6.275 4.925,4.925Q6.275,3.575 8.1,2.787Q9.925,2 12,2Q14.075,2 15.9,2.787Q17.725,3.575 19.075,4.925Q20.425,6.275 21.212,8.1Q22,9.925 22,12Q22,14.075 21.212,15.9Q20.425,17.725 19.075,19.075Q17.725,20.425 15.9,21.212Q14.075,22 12,22Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_palette_24px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_palette_24px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M480,880Q398,880 325,848.5Q252,817 197.5,762.5Q143,708 111.5,635Q80,562 80,480Q80,397 112.5,324Q145,251 200.5,197Q256,143 330,111.5Q404,80 488,80Q568,80 639,107.5Q710,135 763.5,183.5Q817,232 848.5,298.5Q880,365 880,442Q880,557 810,618.5Q740,680 640,680L566,680Q557,680 553.5,685Q550,690 550,696Q550,708 565,730.5Q580,753 580,782Q580,832 552.5,856Q525,880 480,880ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM260,520Q286,520 303,503Q320,486 320,460Q320,434 303,417Q286,400 260,400Q234,400 217,417Q200,434 200,460Q200,486 217,503Q234,520 260,520ZM380,360Q406,360 423,343Q440,326 440,300Q440,274 423,257Q406,240 380,240Q354,240 337,257Q320,274 320,300Q320,326 337,343Q354,360 380,360ZM580,360Q606,360 623,343Q640,326 640,300Q640,274 623,257Q606,240 580,240Q554,240 537,257Q520,274 520,300Q520,326 537,343Q554,360 580,360ZM700,520Q726,520 743,503Q760,486 760,460Q760,434 743,417Q726,400 700,400Q674,400 657,417Q640,434 640,460Q640,486 657,503Q674,520 700,520ZM480,800Q489,800 494.5,795Q500,790 500,782Q500,768 485,749Q470,730 470,692Q470,650 499,625Q528,600 570,600L640,600Q706,600 753,561.5Q800,523 800,442Q800,321 707.5,240.5Q615,160 488,160Q352,160 256,253Q160,346 160,480Q160,613 253.5,706.5Q347,800 480,800Z"/>
|
||||
</vector>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/ic_star_fill1_24px" android:state_selected="true"/>
|
||||
<item android:drawable="@drawable/ic_star_24px"/>
|
||||
</selector>
|
||||
@@ -33,7 +33,7 @@
|
||||
<layer-list>
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="?colorM3PrimaryContainer"/>
|
||||
<solid android:color="@color/bg_compose_poll_style_inactive"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<layer-list>
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="?colorM3PrimaryContainer"/>
|
||||
<solid android:color="@color/bg_compose_poll_style_inactive"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
|
||||
@@ -1,27 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:gravity="center_vertical">
|
||||
<item android:gravity="center_vertical" android:left="-4dp" android:right="-4dp">
|
||||
<shape>
|
||||
<solid android:color="#69ffffff"/>
|
||||
<corners android:radius="1dp"/>
|
||||
<size android:height="2dp"/>
|
||||
<solid android:color="#40ffffff"/>
|
||||
<corners android:radius="16dp"/>
|
||||
<size android:height="8dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item android:gravity="center_vertical" android:id="@android:id/secondaryProgress">
|
||||
<clip>
|
||||
<shape>
|
||||
<solid android:color="#40ffffff"/>
|
||||
<corners android:radius="1dp"/>
|
||||
<size android:height="2dp"/>
|
||||
</shape>
|
||||
</clip>
|
||||
<item android:gravity="center_vertical|end" android:width="4dp" android:height="4dp" android:end="-2dp">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="#fff"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item android:gravity="center_vertical" android:id="@android:id/progress">
|
||||
<item android:gravity="center_vertical" android:id="@android:id/progress" android:left="-4dp" android:right="-4dp">
|
||||
<clip>
|
||||
<shape>
|
||||
<solid android:color="#fff"/>
|
||||
<corners android:radius="1dp"/>
|
||||
<size android:height="2dp"/>
|
||||
<corners android:radius="16dp"/>
|
||||
<size android:height="8dp"/>
|
||||
</shape>
|
||||
</clip>
|
||||
</item>
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||
<solid android:color="#ffffff"/>
|
||||
<size android:width="18dp" android:height="18dp"/>
|
||||
</shape>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/window_bg_alpha95"/>
|
||||
</shape>
|
||||
79
mastodon/src/main/res/layout/avatar_cropper.xml
Normal file
79
mastodon/src/main/res/layout/avatar_cropper.xml
Normal file
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.joinmastodon.android.ui.views.WindowRootFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<org.joinmastodon.android.ui.photoviewer.ZoomPanView
|
||||
android:id="@+id/zoom_pan_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false">
|
||||
<ImageView
|
||||
android:id="@+id/image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:importantForAccessibility="no"/>
|
||||
</org.joinmastodon.android.ui.photoviewer.ZoomPanView>
|
||||
|
||||
<me.grishka.appkit.views.FragmentRootLinearLayout
|
||||
android:id="@+id/overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:actionBarSize"
|
||||
android:layout_gravity="top"
|
||||
android:orientation="horizontal"
|
||||
android:paddingEnd="56dp">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_back"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginHorizontal="4dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:tint="?colorM3OnSurfaceVariant"
|
||||
android:src="@drawable/ic_baseline_close_24"
|
||||
android:contentDescription="@string/back"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:singleLine="true"
|
||||
android:textSize="22dp"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:text="@string/avatar_move_and_scale"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</me.grishka.appkit.views.FragmentRootLinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/btn_confirm"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="bottom|center_horizontal"
|
||||
android:minWidth="152dp"
|
||||
android:paddingStart="16dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:drawableStart="@drawable/ic_check_24px"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled"
|
||||
android:background="@null"
|
||||
android:padding="0dp"
|
||||
android:drawablePadding="7dp"
|
||||
android:drawableTint="@color/button_text_m3_filled"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:text="@string/confirm_avatar_crop"/>
|
||||
</FrameLayout>
|
||||
|
||||
</org.joinmastodon.android.ui.views.WindowRootFrameLayout>
|
||||
@@ -22,9 +22,9 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_label_large"
|
||||
android:textColor="?colorM3Secondary"
|
||||
android:textColor="?colorM3Outline"
|
||||
android:background="@drawable/bg_button_borderless_rounded"
|
||||
android:backgroundTint="?colorM3Secondary"
|
||||
android:backgroundTint="?colorM3Outline"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:paddingVertical="4dp"
|
||||
@@ -37,7 +37,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_label_large"
|
||||
android:textColor="?colorM3Secondary"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:importantForAccessibility="no"
|
||||
android:text="·"/>
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_label_large"
|
||||
android:textColor="?colorM3Secondary"
|
||||
android:textColor="?colorM3Outline"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
tools:text="2023-11-08"/>
|
||||
@@ -56,7 +56,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_label_large"
|
||||
android:textColor="?colorM3Secondary"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:importantForAccessibility="no"
|
||||
android:text="·"/>
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_label_large"
|
||||
android:textColor="?colorM3Secondary"
|
||||
android:textColor="?colorM3Outline"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:paddingVertical="4dp"
|
||||
@@ -73,7 +73,7 @@
|
||||
android:layout_marginVertical="-4dp"
|
||||
android:layout_marginHorizontal="-8dp"
|
||||
android:background="@drawable/bg_button_borderless_rounded"
|
||||
android:backgroundTint="?colorM3Secondary"
|
||||
android:backgroundTint="?colorM3Outline"
|
||||
tools:text="Mastodon for Android dfjklafjdsalkfjdslakfjdsaklfjdslak"/>
|
||||
|
||||
</org.joinmastodon.android.ui.views.WrappingLinearLayout>
|
||||
@@ -86,7 +86,7 @@
|
||||
android:paddingEnd="24dp"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:textAppearance="@style/m3_label_large"
|
||||
android:textColor="?colorM3Secondary"
|
||||
android:textColor="?colorM3Outline"
|
||||
android:gravity="center_vertical"
|
||||
tools:text="Last edit bla bla"/>
|
||||
|
||||
@@ -104,9 +104,9 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_label_large"
|
||||
android:textColor="?colorM3Secondary"
|
||||
android:textColor="?colorM3Outline"
|
||||
android:background="@drawable/bg_button_borderless_rounded"
|
||||
android:backgroundTint="?colorM3Secondary"
|
||||
android:backgroundTint="?colorM3Outline"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:paddingVertical="4dp"
|
||||
@@ -120,9 +120,9 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_label_large"
|
||||
android:textColor="?colorM3Secondary"
|
||||
android:textColor="?colorM3Outline"
|
||||
android:background="@drawable/bg_button_borderless_rounded"
|
||||
android:backgroundTint="?colorM3Secondary"
|
||||
android:backgroundTint="?colorM3Outline"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:paddingVertical="4dp"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:background="@drawable/bg_status_action"
|
||||
android:minWidth="64dp">
|
||||
<TextView
|
||||
android:id="@+id/reply"
|
||||
@@ -43,7 +43,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginHorizontal="4dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:background="@drawable/bg_status_action"
|
||||
android:minWidth="64dp">
|
||||
<TextView
|
||||
android:id="@+id/boost"
|
||||
@@ -72,7 +72,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginHorizontal="4dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:background="@drawable/bg_status_action"
|
||||
android:minWidth="64dp">
|
||||
<TextView
|
||||
android:id="@+id/favorite"
|
||||
@@ -100,7 +100,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="4dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:background="@drawable/bg_status_action_centered"
|
||||
android:minWidth="34dp">
|
||||
<ImageView
|
||||
android:id="@+id/share"
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<me.grishka.appkit.views.FragmentRootLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/appkit_loader_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:windowBackground"
|
||||
android:orientation="vertical">
|
||||
|
||||
<org.joinmastodon.android.ui.views.NestedRecyclerScrollView
|
||||
android:id="@+id/scroller"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<org.joinmastodon.android.ui.views.TopBarsScrollAwayLinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/appkit_toolbar" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/appkit_loader_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include
|
||||
android:id="@+id/loading"
|
||||
layout="@layout/loading" />
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/error"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout="?errorViewLayout"
|
||||
android:visibility="gone" />
|
||||
|
||||
<View
|
||||
android:id="@+id/content_stub"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</FrameLayout>
|
||||
</org.joinmastodon.android.ui.views.TopBarsScrollAwayLinearLayout>
|
||||
</org.joinmastodon.android.ui.views.NestedRecyclerScrollView>
|
||||
</me.grishka.appkit.views.FragmentRootLinearLayout>
|
||||
@@ -39,6 +39,7 @@
|
||||
android:background="?colorM3SecondaryContainer"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/avatar_border"
|
||||
android:layout_width="104dp"
|
||||
android:layout_height="104dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
|
||||
@@ -20,19 +20,31 @@
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/empty"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/fab"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="@drawable/bg_fab"
|
||||
android:tint="?colorM3Primary"
|
||||
android:scaleType="center"
|
||||
android:stateListAnimator="@animator/fab_shadow"
|
||||
android:contentDescription="@string/new_post"
|
||||
android:src="@drawable/ic_edit_24px"/>
|
||||
<FrameLayout
|
||||
android:id="@+id/bottom_overlays"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<ImageButton
|
||||
android:id="@+id/fab"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="@drawable/bg_fab"
|
||||
android:tint="?colorM3Primary"
|
||||
android:scaleType="center"
|
||||
android:stateListAnimator="@animator/fab_shadow"
|
||||
android:contentDescription="@string/new_post"
|
||||
android:src="@drawable/ic_edit_24px"/>
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/donation_banner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout="@layout/donation_banner"/>
|
||||
</FrameLayout>
|
||||
|
||||
<org.joinmastodon.android.ui.views.NewPostsButtonContainer
|
||||
android:id="@+id/new_posts_btn_wrap"
|
||||
@@ -58,12 +70,5 @@
|
||||
android:text="@string/see_new_posts"/>
|
||||
</org.joinmastodon.android.ui.views.NewPostsButtonContainer>
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/donation_banner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout="@layout/donation_banner"/>
|
||||
|
||||
</FrameLayout>
|
||||
</me.grishka.appkit.views.RecursiveSwipeRefreshLayout>
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<me.grishka.appkit.views.FragmentRootLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:id="@+id/photo_viewer_overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -13,66 +14,222 @@
|
||||
<FrameLayout
|
||||
android:id="@+id/toolbar_wrap"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top"
|
||||
android:background="#80000000">
|
||||
android:layout_height="?android:actionBarSize"
|
||||
android:layout_gravity="top">
|
||||
|
||||
<Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:attr/actionBarSize"
|
||||
android:elevation="0dp"
|
||||
android:navigationIcon="@drawable/ic_arrow_back"
|
||||
android:navigationContentDescription="@string/back"
|
||||
android:theme="@style/Theme.Mastodon.Toolbar.Profile"
|
||||
android:background="@null"/>
|
||||
<ImageButton
|
||||
android:id="@+id/btn_back"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="center_vertical|start"
|
||||
android:layout_marginStart="8dp"
|
||||
android:src="@drawable/ic_close_20px"
|
||||
android:tint="#fff"
|
||||
android:background="@drawable/bg_photo_viewer_toolbar_button"
|
||||
android:contentDescription="@string/back"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_download"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:src="@drawable/ic_download_20px"
|
||||
android:tint="#fff"
|
||||
android:background="@drawable/bg_photo_viewer_toolbar_button"
|
||||
android:contentDescription="@string/download"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/video_player_controls"
|
||||
<LinearLayout
|
||||
android:id="@+id/bottom_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:background="#80000000">
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="64dp"
|
||||
android:background="@drawable/bg_photo_viewer_bottom"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/seekbar"
|
||||
<RelativeLayout
|
||||
android:id="@+id/video_player_controls"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="34dp"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/play_pause_btn"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginStart="8dp"
|
||||
android:src="@drawable/ic_pause_24"
|
||||
android:tint="#fff"
|
||||
android:contentDescription="@string/pause"
|
||||
android:background="?android:selectableItemBackgroundBorderless"/>
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/seekbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:layout_toEndOf="@id/play_pause_btn"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:max="10000"
|
||||
android:splitTrack="false"
|
||||
android:layerType="hardware"
|
||||
android:background="@null"
|
||||
android:progressDrawable="@drawable/seekbar_video_player"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="24dp"
|
||||
android:layout_alignStart="@id/seekbar"
|
||||
android:layout_alignTop="@id/seekbar"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/m3_title_medium"
|
||||
android:textColor="#e6ffffff"
|
||||
android:fontFeatureSettings="'tnum'"
|
||||
android:textStyle="bold"
|
||||
android:visibility="invisible"
|
||||
tools:text="1:23 / 4:56"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<org.joinmastodon.android.ui.views.PhotoViewerAltTextView
|
||||
android:id="@+id/alt_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="#E6FFFFFF"
|
||||
android:ellipsize="end"
|
||||
tools:text="Alt text goes here"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/post_actions"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginBottom="-8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:max="10000"
|
||||
android:progressDrawable="@drawable/seekbar_video_player"
|
||||
android:thumb="@drawable/seekbar_video_player_thumb"/>
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/play_pause_btn"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_below="@id/seekbar"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:src="@drawable/ic_pause_24"
|
||||
android:tint="#fff"
|
||||
android:contentDescription="@string/pause"
|
||||
android:background="?android:selectableItemBackgroundBorderless"/>
|
||||
<FrameLayout
|
||||
android:id="@+id/reply_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:background="@drawable/bg_photo_viewer_action"
|
||||
android:minWidth="64dp">
|
||||
<TextView
|
||||
android:id="@+id/reply"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center|start"
|
||||
android:drawableStart="@drawable/ic_reply_20px"
|
||||
android:drawablePadding="6dp"
|
||||
android:drawableTint="#80ffffff"
|
||||
android:textColor="#80ffffff"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/m3_label_medium"
|
||||
android:duplicateParentState="true"
|
||||
tools:text="123"/>
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="24dp"
|
||||
android:layout_below="@id/seekbar"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:textColor="#fff"
|
||||
tools:text="1:23 / 4:56"/>
|
||||
<FrameLayout
|
||||
android:id="@+id/boost_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:background="@drawable/bg_photo_viewer_action"
|
||||
android:minWidth="64dp">
|
||||
<TextView
|
||||
android:id="@+id/boost"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center|start"
|
||||
android:drawableStart="@drawable/ic_repeat_selector"
|
||||
android:drawablePadding="6dp"
|
||||
android:drawableTint="#80ffffff"
|
||||
android:textColor="#80ffffff"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/m3_label_medium"
|
||||
android:duplicateParentState="true"
|
||||
tools:text="123"/>
|
||||
</FrameLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
<FrameLayout
|
||||
android:id="@+id/favorite_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:background="@drawable/bg_photo_viewer_action"
|
||||
android:minWidth="64dp">
|
||||
<TextView
|
||||
android:id="@+id/favorite"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center|start"
|
||||
android:drawableStart="@drawable/ic_star_selector"
|
||||
android:drawablePadding="6dp"
|
||||
android:drawableTint="#80ffffff"
|
||||
android:textColor="#80ffffff"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/m3_label_medium"
|
||||
android:duplicateParentState="true"
|
||||
tools:text="123"/>
|
||||
</FrameLayout>
|
||||
|
||||
<Space
|
||||
android:layout_width="0px"
|
||||
android:layout_height="1px"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/bookmark_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:background="@drawable/bg_photo_viewer_action_centered"
|
||||
android:minWidth="34dp">
|
||||
<ImageView
|
||||
android:id="@+id/bookmark"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/ic_bookmark_20px_selector"
|
||||
android:tint="#80ffffff"
|
||||
android:tintMode="src_in"
|
||||
android:duplicateParentState="true"
|
||||
android:gravity="center_vertical"/>
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/share_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:background="@drawable/bg_photo_viewer_action_centered"
|
||||
android:minWidth="34dp">
|
||||
<ImageView
|
||||
android:id="@+id/share"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/ic_share_20px"
|
||||
android:tint="#80ffffff"
|
||||
android:tintMode="src_in"
|
||||
android:contentDescription="@string/share_toot_title"
|
||||
android:gravity="center_vertical"/>
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.joinmastodon.android.ui.views.CustomScrollView 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"
|
||||
android:background="@drawable/bg_bottom_sheet"
|
||||
android:outlineProvider="background"
|
||||
android:elevation="1dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<View
|
||||
android:id="@+id/handle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="36dp"
|
||||
android:background="@drawable/bg_bottom_sheet_handle"/>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/buttons_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="64dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:baselineAligned="false">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_boost"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:text="@string/button_reblog"
|
||||
android:drawableTop="@drawable/ic_boost"
|
||||
style="@style/Widget.Mastodon.M3.Button.IconWithLabel"/>
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_favorite"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:text="@string/button_favorite"
|
||||
android:drawableTop="@drawable/ic_star_24px_selector"
|
||||
style="@style/Widget.Mastodon.M3.Button.IconWithLabel"/>
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_share"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:text="@string/button_share"
|
||||
android:drawableTop="@drawable/ic_share_24px"
|
||||
style="@style/Widget.Mastodon.M3.Button.IconWithLabel"/>
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_bookmark"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:text="@string/add_bookmark"
|
||||
android:drawableTop="@drawable/ic_bookmark_24px_selector"
|
||||
style="@style/Widget.Mastodon.M3.Button.IconWithLabel"/>
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_download"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:text="@string/download"
|
||||
android:drawableTop="@drawable/ic_download_24px"
|
||||
style="@style/Widget.Mastodon.M3.Button.IconWithLabel"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<View
|
||||
android:id="@+id/divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginVertical="4dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:background="?colorM3OutlineVariant"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/alt_text_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginHorizontal="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:textSize="22dp"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:gravity="center_vertical|start"
|
||||
android:text="@string/alt_text"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/alt_text_help"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:tint="?colorM3OnSurfaceVariant"
|
||||
android:contentDescription="@string/help"
|
||||
android:src="@drawable/ic_help_24px"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/alt_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:textIsSelectable="true"
|
||||
tools:text="A cute black cat"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</org.joinmastodon.android.ui.views.CustomScrollView>
|
||||
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/vis_public"
|
||||
android:title="@string/visibility_public"/>
|
||||
<item android:id="@+id/vis_unlisted"
|
||||
android:title="@string/visibility_unlisted"/>
|
||||
<item android:id="@+id/vis_followers"
|
||||
android:title="@string/visibility_followers_only"/>
|
||||
<item android:id="@+id/vis_private"
|
||||
android:title="@string/visibility_private"/>
|
||||
</menu>
|
||||
@@ -128,9 +128,7 @@
|
||||
</plurals>
|
||||
<string name="poll_closed">مغلق</string>
|
||||
<string name="do_mute">اكتم</string>
|
||||
<string name="do_unmute">ارفع الكتم</string>
|
||||
<string name="do_block">احجب</string>
|
||||
<string name="do_unblock">ارفع الحجب</string>
|
||||
<string name="button_blocked">محجوب</string>
|
||||
<string name="action_vote">صَوّت</string>
|
||||
<string name="delete">احذف</string>
|
||||
@@ -204,7 +202,6 @@
|
||||
<string name="add_alt_text">أضف نصًا بديلًا</string>
|
||||
<string name="visibility_public">علني</string>
|
||||
<string name="recent_searches">الحديثة</string>
|
||||
<string name="skip">تخطى</string>
|
||||
<string name="notification_type_follow">متابعُون جُدُد</string>
|
||||
<string name="notification_type_favorite">المفضلة</string>
|
||||
<string name="notification_type_reblog">المعاد نشرها</string>
|
||||
@@ -267,7 +264,6 @@
|
||||
<string name="storage_permission_to_download">يحتاج هذا التطبيق أذن الوصول للتخزين لحفظ الملف.</string>
|
||||
<string name="open_settings">افتح الإعدادات</string>
|
||||
<string name="error_saving_file">خطأ أثناء حفظ الملف</string>
|
||||
<string name="file_saved">حُفظ الملف</string>
|
||||
<string name="downloading">ينزّل…</string>
|
||||
<string name="no_app_to_handle_action">لا يوجد تطبيق لمعالجة هذا الإجراء</string>
|
||||
<string name="trending_posts_info_banner">هذه هي المشاركات التي تكتسب شعبية عبر ماستدون.</string>
|
||||
|
||||
@@ -110,9 +110,7 @@
|
||||
</plurals>
|
||||
<string name="poll_closed">Завершана</string>
|
||||
<string name="do_mute">Ігнараваць</string>
|
||||
<string name="do_unmute">Не ігнараваць</string>
|
||||
<string name="do_block">Заблакіраваць</string>
|
||||
<string name="do_unblock">Разблакіраваць</string>
|
||||
<string name="button_blocked">Заблакіраваны</string>
|
||||
<string name="action_vote">Прагаласаваць</string>
|
||||
<string name="delete">Выдаліць</string>
|
||||
@@ -189,7 +187,6 @@
|
||||
<string name="visibility_followers_only">Падпісчыкам</string>
|
||||
<string name="visibility_private">Канкрэтным людзям</string>
|
||||
<string name="recent_searches">Нядаўняе</string>
|
||||
<string name="skip">Прапусціць</string>
|
||||
<string name="notification_type_follow">Новыя падпісчыкі</string>
|
||||
<string name="notification_type_favorite">Абраныя</string>
|
||||
<string name="notification_type_reblog">Пашырэнні</string>
|
||||
@@ -253,7 +250,6 @@
|
||||
<string name="storage_permission_to_download">Праграме патрэбны доступ да сховішча, каб захаваць гэты файл.</string>
|
||||
<string name="open_settings">Адкрыць налады</string>
|
||||
<string name="error_saving_file">Памылка пры захаванні файла</string>
|
||||
<string name="file_saved">Файл захаваны</string>
|
||||
<string name="downloading">Спампоўванне…</string>
|
||||
<string name="no_app_to_handle_action">Няма патрэбнай праграмы для гэтага дзеяння</string>
|
||||
<string name="local_timeline">Жывая стужка</string>
|
||||
@@ -705,10 +701,8 @@
|
||||
Чым больш людзей на якіх вы падпісаны, тым больш актыўнай і цікавай яна будзе.</string>
|
||||
<string name="onboarding_recommendations_title">Папулярна на Mastodon</string>
|
||||
<string name="article_by_author">Ад %s</string>
|
||||
<string name="info">Звесткі</string>
|
||||
<string name="button_reblogged">Пашыраны</string>
|
||||
<string name="button_favorited">Упадабанае</string>
|
||||
<string name="bookmarked">У закладках</string>
|
||||
<string name="join_server_x_with_invite">Далучайцеся да %s па запрашэнні</string>
|
||||
<string name="expired_invite_link">Тэрмін дзеяння запрашальнай спасылкі скончыўся</string>
|
||||
<string name="expired_clipboard_invite_link_alert">Спасылка запрашэння для %1$s у буферы абмену састарэла і не можа быць выкарыстана для рэгістрацыі.\n\nВы можаце запытаць новую спасылку ў існуючага карыстальніка. Зарэгіструйцеся праз %2$s, або абярыце іншы сервер для рэгістрацыі.</string>
|
||||
@@ -837,4 +831,16 @@
|
||||
<string name="settings_manage_donations">Кіраваць ахвяраваннямі</string>
|
||||
<!-- %1$s is your server domain, %2$s is the domain that was blocked, %3$,d is the follower count, %4$s is the `x_accounts` plural string -->
|
||||
<!-- %1$s is the domain that was blocked, %2$,d is the follower count, %3$s is the `x_accounts` plural string -->
|
||||
<string name="relationship_severance_learn_more">Даведацца больш</string>
|
||||
<string name="moderation_warning_action_none">Ваш уліковы запіс атрымаў папярэджанне ад мадэратараў.</string>
|
||||
<string name="moderation_warning_action_disable">Ваш уліковы запіс быў адключаны.</string>
|
||||
<string name="moderation_warning_action_mark_statuses_as_sensitive">Некаторыя з вашых допісаў былі пазначаныя як далікатныя.</string>
|
||||
<string name="moderation_warning_action_delete_statuses">Некаторыя вашыя допісы былі выдаленыя.</string>
|
||||
<string name="moderation_warning_action_sensitive">З гэтага моманту вашыя допісы будуць пазначаныя як далікатныя.</string>
|
||||
<string name="moderation_warning_action_silence">Ваш уліковы запіс быў абмежаваны.</string>
|
||||
<string name="moderation_warning_action_suspend">Ваш уліковы запіс быў прыпынены.</string>
|
||||
<string name="moderation_warning_learn_more">Даведацца больш</string>
|
||||
<string name="text_show_more">Больш</string>
|
||||
<string name="avatar_move_and_scale">Перасоўванне і маштабаванне</string>
|
||||
<string name="confirm_avatar_crop">Выбраць</string>
|
||||
</resources>
|
||||
|
||||
@@ -70,9 +70,7 @@
|
||||
</plurals>
|
||||
<string name="poll_closed">বন্ধ</string>
|
||||
<string name="do_mute">মিউট করুন</string>
|
||||
<string name="do_unmute">মিউট</string>
|
||||
<string name="do_block">ব্লক করুন</string>
|
||||
<string name="do_unblock">আনব্লক করুন</string>
|
||||
<string name="button_blocked">ব্লক করা আছে</string>
|
||||
<string name="action_vote">ভোট</string>
|
||||
<string name="delete">মুছে ফেলুন</string>
|
||||
@@ -143,7 +141,6 @@
|
||||
<string name="storage_permission_to_download">এই ফাইলটি সেভ করতে অ্যাপটির আপনার স্টোরেজ অ্যাক্সেসের অনুমতি প্রয়োজন.</string>
|
||||
<string name="open_settings">সেটিংস খুলুন</string>
|
||||
<string name="error_saving_file">ফাইল সেভ করতে ত্রুটি দেখা দিচ্ছে</string>
|
||||
<string name="file_saved">ফাইল সেভ হয়েছে</string>
|
||||
<string name="downloading">ডাউনলোড হচ্ছে…</string>
|
||||
<!-- %s is the server domain -->
|
||||
<string name="button_follow_pending">বাকি আছে</string>
|
||||
|
||||
@@ -45,9 +45,7 @@
|
||||
<string name="poll_option_hint">Opcija %d</string>
|
||||
<string name="poll_closed">Zatvoreno</string>
|
||||
<string name="do_mute">Ušuti</string>
|
||||
<string name="do_unmute">Iskljuci šutnju</string>
|
||||
<string name="do_block">Blokiraj</string>
|
||||
<string name="do_unblock">Iskljuci blokadu</string>
|
||||
<string name="button_blocked">Blokiran</string>
|
||||
<string name="action_vote">Glasaj</string>
|
||||
<string name="delete">Briši</string>
|
||||
@@ -88,7 +86,6 @@
|
||||
<string name="save">Sačuvaj</string>
|
||||
<string name="add_alt_text">Dodaj alt tekst</string>
|
||||
<string name="visibility_public">Javno</string>
|
||||
<string name="skip">Preskoči</string>
|
||||
<string name="notification_type_follow">Novi pratioci</string>
|
||||
<string name="notification_type_favorite">Favoriti</string>
|
||||
<string name="notification_type_mention">Spominjanja</string>
|
||||
@@ -133,7 +130,6 @@
|
||||
<string name="storage_permission_to_download">Aplikacija treba pristup vašem uredjaju da sačuva ovu datoteku.</string>
|
||||
<string name="open_settings">Otvori postavke</string>
|
||||
<string name="error_saving_file">Greška prilikom procesiranja</string>
|
||||
<string name="file_saved">Datoteka sačuvana</string>
|
||||
<string name="downloading">Downloading…</string>
|
||||
<!-- %s is the server domain -->
|
||||
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
|
||||
|
||||
@@ -81,9 +81,7 @@
|
||||
</plurals>
|
||||
<string name="poll_closed">Finalitzada</string>
|
||||
<string name="do_mute">Silencia</string>
|
||||
<string name="do_unmute">Deixa de silenciar</string>
|
||||
<string name="do_block">Bloca</string>
|
||||
<string name="do_unblock">Desbloca</string>
|
||||
<string name="button_blocked">Blocat</string>
|
||||
<string name="action_vote">Vota</string>
|
||||
<string name="delete">Elimina</string>
|
||||
@@ -135,7 +133,6 @@
|
||||
<string name="save">Desa</string>
|
||||
<string name="add_alt_text">Afegeix text alternatiu</string>
|
||||
<string name="visibility_public">Públic</string>
|
||||
<string name="skip">Omet</string>
|
||||
<string name="notification_type_follow">Seguidors nous</string>
|
||||
<string name="notification_type_favorite">Favorits</string>
|
||||
<string name="notification_type_mention">Mencions</string>
|
||||
@@ -183,7 +180,6 @@
|
||||
<string name="storage_permission_to_download">L\'aplicació necessita accedir al vostre emmagatzematge per desar aquest fitxer.</string>
|
||||
<string name="open_settings">Obre la configuració</string>
|
||||
<string name="error_saving_file">Error en desar el fitxer</string>
|
||||
<string name="file_saved">Fitxer desat</string>
|
||||
<string name="downloading">S\'està baixant…</string>
|
||||
<!-- %s is the server domain -->
|
||||
<string name="load_missing_posts">Carrega les publicacions faltants</string>
|
||||
|
||||
@@ -110,9 +110,7 @@
|
||||
</plurals>
|
||||
<string name="poll_closed">Uzavřeno</string>
|
||||
<string name="do_mute">Skrýt</string>
|
||||
<string name="do_unmute">Zrušit skrytí</string>
|
||||
<string name="do_block">Blokovat</string>
|
||||
<string name="do_unblock">Odblokovat</string>
|
||||
<string name="button_blocked">Blokovaný</string>
|
||||
<string name="action_vote">Hlasovat</string>
|
||||
<string name="delete">Smazat</string>
|
||||
@@ -189,7 +187,6 @@
|
||||
<string name="visibility_followers_only">Sledující</string>
|
||||
<string name="visibility_private">Vybraní lidé</string>
|
||||
<string name="recent_searches">Nedávné</string>
|
||||
<string name="skip">Přeskočit</string>
|
||||
<string name="notification_type_follow">Noví sledující</string>
|
||||
<string name="notification_type_favorite">Oblíbené</string>
|
||||
<string name="notification_type_reblog">Boosty</string>
|
||||
@@ -253,7 +250,6 @@
|
||||
<string name="storage_permission_to_download">Aplikace potřebuje přístup k vašemu úložišti, aby mohla uložit tento soubor.</string>
|
||||
<string name="open_settings">Otevřít nastavení</string>
|
||||
<string name="error_saving_file">Nastala chyba při ukládání souboru</string>
|
||||
<string name="file_saved">Soubor byl uložen</string>
|
||||
<string name="downloading">Stahování…</string>
|
||||
<string name="no_app_to_handle_action">Nebyly nalezeny žádné aplikace pro tuto úlohu</string>
|
||||
<string name="local_timeline">Živý kanál</string>
|
||||
@@ -705,10 +701,8 @@
|
||||
Čím více lidí budete sledovat, tím bude aktivnější a zajímavější.</string>
|
||||
<string name="onboarding_recommendations_title">Přizpůsobte si svoji zeď</string>
|
||||
<string name="article_by_author">Od: %s</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="button_reblogged">Boostováno</string>
|
||||
<string name="button_favorited">Oblíbené</string>
|
||||
<string name="bookmarked">Záložky</string>
|
||||
<string name="join_server_x_with_invite">Připojte se k %s s pozvánkou</string>
|
||||
<string name="expired_invite_link">Prošlý odkaz pozvánky</string>
|
||||
<string name="expired_clipboard_invite_link_alert">Platnost pozvánky pro %1$s ve vaší schránce vypršela a nelze ji použít k registraci.\n\nMůžete požádat o nový odkaz od existujícího uživatele, přihlásit se přes %2$s, nebo si vybrat jiný server k přihlášení.</string>
|
||||
|
||||
@@ -128,9 +128,7 @@
|
||||
</plurals>
|
||||
<string name="poll_closed">Wedi cau</string>
|
||||
<string name="do_mute">Anwybyddu</string>
|
||||
<string name="do_unmute">Dad-anwybyddu</string>
|
||||
<string name="do_block">Blocio</string>
|
||||
<string name="do_unblock">Dadflocio</string>
|
||||
<string name="button_blocked">Wedi blocio</string>
|
||||
<string name="action_vote">Pleidleisio</string>
|
||||
<string name="delete">Dileu</string>
|
||||
@@ -202,7 +200,6 @@
|
||||
<string name="visibility_followers_only">Dilynwyr</string>
|
||||
<string name="visibility_private">Pobl benodol</string>
|
||||
<string name="recent_searches">Diweddar</string>
|
||||
<string name="skip">Hepgor</string>
|
||||
<string name="notification_type_follow">Dilynwyr newydd</string>
|
||||
<string name="notification_type_favorite">Ffefrynnau</string>
|
||||
<string name="notification_type_reblog">Hybiau</string>
|
||||
@@ -254,7 +251,6 @@
|
||||
<string name="permission_required">Angen caniatâd</string>
|
||||
<string name="open_settings">Agor gosodiadau</string>
|
||||
<string name="error_saving_file">Gwall wrth gadw ffeil</string>
|
||||
<string name="file_saved">Cadwyd y ffeil</string>
|
||||
<string name="downloading">Wrthi\'n lawrlwytho…</string>
|
||||
<string name="local_timeline">Ffrwd fyw</string>
|
||||
<string name="trending_posts_info_banner">Dyma\'r postiadau sy\'n denu tipyn o sylw ar draws Mastodon.</string>
|
||||
@@ -598,10 +594,8 @@
|
||||
<string name="non_mutual_title3">Byddwch yn agored</string>
|
||||
<string name="onboarding_recommendations_title">Personolwch eich ffrwd gartref</string>
|
||||
<string name="article_by_author">Gan %s</string>
|
||||
<string name="info">Gwybodaeth</string>
|
||||
<string name="button_reblogged">Hybwyd</string>
|
||||
<string name="button_favorited">Ffafriwyd</string>
|
||||
<string name="bookmarked">Tudalnodwyd</string>
|
||||
<string name="join_server_x_with_invite">Ymuno â %s gyda gwahoddiad</string>
|
||||
<string name="use_invite_link">Defnyddio dolen wahodd</string>
|
||||
<string name="enter_invite_link">Mewnosod dolen wahodd</string>
|
||||
|
||||
@@ -88,9 +88,7 @@
|
||||
</plurals>
|
||||
<string name="poll_closed">Lukket</string>
|
||||
<string name="do_mute">Tavsgør</string>
|
||||
<string name="do_unmute">Ophæv tavsgørelse</string>
|
||||
<string name="do_block">Blokér</string>
|
||||
<string name="do_unblock">Afblokér</string>
|
||||
<string name="button_blocked">Blokeret</string>
|
||||
<string name="action_vote">Stem</string>
|
||||
<string name="delete">Slet</string>
|
||||
@@ -165,7 +163,6 @@
|
||||
<string name="visibility_followers_only">Følgere</string>
|
||||
<string name="visibility_private">Bestemte folk</string>
|
||||
<string name="recent_searches">Nylige</string>
|
||||
<string name="skip">Overspring</string>
|
||||
<string name="notification_type_follow">Nye Følgere</string>
|
||||
<string name="notification_type_favorite">Favoritmarkeringer</string>
|
||||
<string name="notification_type_reblog">Fremhævninger</string>
|
||||
@@ -227,7 +224,6 @@
|
||||
<string name="storage_permission_to_download">Appen skal have adgang til din lagerplads for at gemme denne fil.</string>
|
||||
<string name="open_settings">Åbn Indstillinger</string>
|
||||
<string name="error_saving_file">Fejl under lagring af fil</string>
|
||||
<string name="file_saved">Fil gemt</string>
|
||||
<string name="downloading">Downloader…</string>
|
||||
<string name="no_app_to_handle_action">Ingen app til at håndtere denne handling</string>
|
||||
<string name="local_timeline">Realtids-feed</string>
|
||||
@@ -639,10 +635,8 @@
|
||||
Jo flere folk, man følger, jo mere aktivt og interessant vil det være.</string>
|
||||
<string name="onboarding_recommendations_title">Personliggør hjemme-feedet</string>
|
||||
<string name="article_by_author">Af %s</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="button_reblogged">Boostet</string>
|
||||
<string name="button_favorited">Gjort til favorit</string>
|
||||
<string name="bookmarked">Bogmærket</string>
|
||||
<string name="join_server_x_with_invite">Tilmeld %s med invitation</string>
|
||||
<string name="expired_invite_link">Udløbet invitationslink</string>
|
||||
<string name="expired_clipboard_invite_link_alert">Invitationslinket til %1$s i udklipsholderen er udløbet og kan ikke bruges til tilmelding.\n\nDer kan anmodes om et nyt link fra en eksisterende bruger, tilmelde via %2$s eller vælges en anden server for tilmelding.</string>
|
||||
|
||||
@@ -90,9 +90,7 @@
|
||||
</plurals>
|
||||
<string name="poll_closed">Beendet</string>
|
||||
<string name="do_mute">Stummschalten</string>
|
||||
<string name="do_unmute">Nicht mehr stummschalten</string>
|
||||
<string name="do_block">Sperren</string>
|
||||
<string name="do_unblock">Nicht mehr blockieren</string>
|
||||
<string name="button_blocked">Blockiert</string>
|
||||
<string name="action_vote">Abstimmen</string>
|
||||
<string name="delete">Löschen</string>
|
||||
@@ -167,7 +165,6 @@
|
||||
<string name="visibility_followers_only">Folgende</string>
|
||||
<string name="visibility_private">Bestimmte Personen</string>
|
||||
<string name="recent_searches">Verlauf</string>
|
||||
<string name="skip">Überspringen</string>
|
||||
<string name="notification_type_follow">Neue Follower</string>
|
||||
<string name="notification_type_favorite">Favoriten</string>
|
||||
<string name="notification_type_reblog">Geteilte Beiträge</string>
|
||||
@@ -229,7 +226,6 @@
|
||||
<string name="storage_permission_to_download">Die App benötigt Zugriff auf den Speicher deines Geräts, um diese Datei zu speichern.</string>
|
||||
<string name="open_settings">Einstellungen öffnen</string>
|
||||
<string name="error_saving_file">Fehler beim Speichern der Datei</string>
|
||||
<string name="file_saved">Datei gespeichert</string>
|
||||
<string name="downloading">wird heruntergeladen …</string>
|
||||
<string name="no_app_to_handle_action">Es gibt keine App, um diese Aktion auszuführen</string>
|
||||
<string name="local_timeline">Live-Feed</string>
|
||||
@@ -640,10 +636,8 @@
|
||||
<string name="onboarding_recommendations_intro">Du bestimmst selbst über deine Startseite. Je mehr Leuten du folgst, umso aktiver und interessanter wird sie sein.</string>
|
||||
<string name="onboarding_recommendations_title">Gestalte deine Startseite</string>
|
||||
<string name="article_by_author">Von %s</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="button_reblogged">Geteilt</string>
|
||||
<string name="button_favorited">Favorisiert</string>
|
||||
<string name="bookmarked">Gemerkt</string>
|
||||
<string name="join_server_x_with_invite">%s mit Einladung beitreten</string>
|
||||
<string name="expired_invite_link">Abgelaufener Einladungslink</string>
|
||||
<string name="expired_clipboard_invite_link_alert">Der Einladungslink für %1$s in Ihrer Zwischenablage ist abgelaufen und kann nicht verwendet werden, um sich anzumelden.\n\nSie können einen neuen Link von einem existierenden Benutzer anfordern, Melden Sie sich über %2$s an, oder wählen Sie einen anderen Server, um sich anzumelden.</string>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<string name="notifications">Ειδοποιήσεις</string>
|
||||
<string name="user_followed_you">%s σε ακολούθησε</string>
|
||||
<string name="user_sent_follow_request">%s σού έστειλε ένα αίτημα ακολούθησης</string>
|
||||
<string name="notification_boosted">Ο χρήστης %s ενίσχυσε:</string>
|
||||
<string name="share_toot_title">Κοινοποίηση</string>
|
||||
<string name="settings">Ρυθμίσεις</string>
|
||||
<string name="publish">Δημοσίευση</string>
|
||||
@@ -88,9 +89,7 @@
|
||||
</plurals>
|
||||
<string name="poll_closed">Κλειστό</string>
|
||||
<string name="do_mute">Σίγαση</string>
|
||||
<string name="do_unmute">Κατάργηση σίγασης</string>
|
||||
<string name="do_block">Αποκλεισμός</string>
|
||||
<string name="do_unblock">Άρση αποκλεισμού</string>
|
||||
<string name="button_blocked">Αποκλείστηκε</string>
|
||||
<string name="action_vote">Ψήφισε</string>
|
||||
<string name="delete">Διαγραφή</string>
|
||||
@@ -165,7 +164,6 @@
|
||||
<string name="visibility_followers_only">Ακόλουθοι</string>
|
||||
<string name="visibility_private">Συγκεκριμένα άτομα</string>
|
||||
<string name="recent_searches">Πρόσφατα</string>
|
||||
<string name="skip">Παράλειψη</string>
|
||||
<string name="notification_type_follow">Νέοι ακόλουθοι</string>
|
||||
<string name="notification_type_favorite">Αγαπημένα</string>
|
||||
<string name="notification_type_reblog">Ενισχύσεις</string>
|
||||
@@ -227,7 +225,6 @@
|
||||
<string name="storage_permission_to_download">Η εφαρμογή χρειάζεται πρόσβαση στον αποθηκευτικό σου χώρο για να αποθηκεύσει αυτό το αρχείο.</string>
|
||||
<string name="open_settings">Άνοιγμα ρυθμίσεων</string>
|
||||
<string name="error_saving_file">Σφάλμα αποθήκευσης αρχείου</string>
|
||||
<string name="file_saved">Το αρχείο αποθηκεύτηκε</string>
|
||||
<string name="downloading">Λήψη…</string>
|
||||
<string name="no_app_to_handle_action">Δεν υπάρχει εφαρμογή για να χειριστεί αυτήν την ενέργεια</string>
|
||||
<string name="local_timeline">Ζωντανή ροή</string>
|
||||
@@ -638,10 +635,8 @@
|
||||
<string name="onboarding_recommendations_intro">Εσύ επιμελείσαι την Αρχική σου ροή. Όσο περισσότερους ανθρώπους ακολουθείς, τόσο πιο δραστήρια και ενδιαφέρουσα θα είναι.</string>
|
||||
<string name="onboarding_recommendations_title">Προσάρμοσε την αρχική ροή σου</string>
|
||||
<string name="article_by_author">Βάσει %s</string>
|
||||
<string name="info">Πληροφορίες</string>
|
||||
<string name="button_reblogged">Ενισχύθηκε</string>
|
||||
<string name="button_favorited">Είναι αγαπημένο</string>
|
||||
<string name="bookmarked">Σε σελιδοδείκτη</string>
|
||||
<string name="join_server_x_with_invite">Συμμετοχή σε %s με πρόσκληση</string>
|
||||
<string name="expired_invite_link">Λιγμένος σύνδεσμος πρόσκλησης</string>
|
||||
<string name="expired_clipboard_invite_link_alert">Ο σύνδεσμος πρόσκλησης %1$s στο πρόχειρό σου έχει λήξει και δεν μπορεί να χρησιμοποιηθεί για εγγραφή.\n\nΜπορείς να αιτηθείς νέο σύνδεσμο από υφιστάμενο χρήστη, να εγγραφείς μέσω %2$s ή επέλεξε άλλον διακομιστή να εγγραφείς.</string>
|
||||
@@ -765,6 +760,22 @@
|
||||
<string name="settings_donate">Συνείσφερε στο Mastodon</string>
|
||||
<string name="settings_manage_donations">Διαχείριση δωρεών</string>
|
||||
<string name="cant_load_image">Αδυναμία φόρτωσης εικόνας</string>
|
||||
<string name="poll_see_results">Εμφάνιση αποτελεσμάτων</string>
|
||||
<string name="poll_hide_results">Απόκρυψη αποτελεσμάτων</string>
|
||||
<plurals name="x_attachments">
|
||||
<item quantity="one">%d συνημμένο</item>
|
||||
<item quantity="other">%d συνημμένα</item>
|
||||
</plurals>
|
||||
<string name="own_poll_ended">Η δημοσκόπησή σου έληξε</string>
|
||||
<!-- %1$s is your server domain, %2$s is the domain that was blocked, %3$,d is the follower count, %4$s is the `x_accounts` plural string -->
|
||||
<!-- %1$s is the domain that was blocked, %2$,d is the follower count, %3$s is the `x_accounts` plural string -->
|
||||
<string name="relationship_severance_learn_more">Μάθε περισσότερα</string>
|
||||
<string name="moderation_warning_action_none">Ο λογαριασμός σου έχει λάβει προειδοποίηση συντονισμού.</string>
|
||||
<string name="moderation_warning_action_disable">Ο λογαριασμός σου έχει απενεργοποιηθεί.</string>
|
||||
<string name="moderation_warning_action_mark_statuses_as_sensitive">Μερικές από τις αναρτήσεις σου έχουν επισημανθεί ως ευαίσθητες.</string>
|
||||
<string name="moderation_warning_action_delete_statuses">Ορισμένες από τις αναρτήσεις σου έχουν αφαιρεθεί.</string>
|
||||
<string name="moderation_warning_action_sensitive">Οι αναρτήσεις σου θα επισημαίνονται ως ευαίσθητες από \'δω και στο εξής.</string>
|
||||
<string name="moderation_warning_action_silence">Ο λογαριασμός σου έχει περιοριστεί.</string>
|
||||
<string name="moderation_warning_action_suspend">Ο λογαριασμός σου έχει ανασταλεί.</string>
|
||||
<string name="moderation_warning_learn_more">Μάθε περισσότερα</string>
|
||||
</resources>
|
||||
|
||||
@@ -90,9 +90,7 @@
|
||||
</plurals>
|
||||
<string name="poll_closed">Cerrado</string>
|
||||
<string name="do_mute">Silenciar</string>
|
||||
<string name="do_unmute">Dejar de silenciar</string>
|
||||
<string name="do_block">Bloquear</string>
|
||||
<string name="do_unblock">Desbloquear</string>
|
||||
<string name="button_blocked">Bloqueado</string>
|
||||
<string name="action_vote">Votar</string>
|
||||
<string name="delete">Eliminar</string>
|
||||
@@ -167,7 +165,6 @@
|
||||
<string name="visibility_followers_only">Seguidores</string>
|
||||
<string name="visibility_private">Personas específicas</string>
|
||||
<string name="recent_searches">Recientes</string>
|
||||
<string name="skip">Saltar</string>
|
||||
<string name="notification_type_follow">Nuevos seguidores</string>
|
||||
<string name="notification_type_favorite">Favoritos</string>
|
||||
<string name="notification_type_reblog">Impulsos</string>
|
||||
@@ -229,7 +226,6 @@
|
||||
<string name="storage_permission_to_download">La aplicación necesita acceso al almacenamiento para guardar este archivo.</string>
|
||||
<string name="open_settings">Abrir la configuración</string>
|
||||
<string name="error_saving_file">Error al guardar el archivo</string>
|
||||
<string name="file_saved">Archivo guardado</string>
|
||||
<string name="downloading">Descargando…</string>
|
||||
<string name="no_app_to_handle_action">No hay ninguna aplicación para manejar esta acción</string>
|
||||
<string name="local_timeline">Feed en vivo</string>
|
||||
@@ -641,10 +637,8 @@
|
||||
Mientras más personas sigas, más activo e interesante será.</string>
|
||||
<string name="onboarding_recommendations_title">Personaliza tu feed de inicio</string>
|
||||
<string name="article_by_author">Por %s</string>
|
||||
<string name="info">Información</string>
|
||||
<string name="button_reblogged">Impulsado</string>
|
||||
<string name="button_favorited">Añadido a favoritos</string>
|
||||
<string name="bookmarked">Añadido a marcadores</string>
|
||||
<string name="join_server_x_with_invite">Únete a %s con invitación</string>
|
||||
<string name="expired_invite_link">Enlace de invitación expirado</string>
|
||||
<string name="expired_clipboard_invite_link_alert">El enlace de invitación para %1$s en tu portapapeles ha expirado y no puede ser utilizado para registrarse.\n\nPuedes solicitar un nuevo enlace de un usuario existente y registrarte a través de %2$s, elegir otro servidor para registrarte.</string>
|
||||
@@ -807,4 +801,5 @@ Mientras más personas sigas, más activo e interesante será.</string>
|
||||
<string name="moderation_warning_action_silence">Tu cuenta ha sido limitada.</string>
|
||||
<string name="moderation_warning_action_suspend">Tu cuenta ha sido suspendida.</string>
|
||||
<string name="moderation_warning_learn_more">Saber más</string>
|
||||
<string name="text_show_more">Más</string>
|
||||
</resources>
|
||||
|
||||
@@ -90,9 +90,7 @@
|
||||
</plurals>
|
||||
<string name="poll_closed">Itxita</string>
|
||||
<string name="do_mute">Mututu</string>
|
||||
<string name="do_unmute">Desmututu</string>
|
||||
<string name="do_block">Blokeatu</string>
|
||||
<string name="do_unblock">Desblokeatu</string>
|
||||
<string name="button_blocked">Blokeatuta</string>
|
||||
<string name="action_vote">Bozkatu</string>
|
||||
<string name="delete">Ezabatu</string>
|
||||
@@ -167,7 +165,6 @@
|
||||
<string name="visibility_followers_only">Jarraitzaileak</string>
|
||||
<string name="visibility_private">Jende jakina</string>
|
||||
<string name="recent_searches">Azkenaldikoak</string>
|
||||
<string name="skip">Saltatu</string>
|
||||
<string name="notification_type_follow">Jarraitzaile berriak</string>
|
||||
<string name="notification_type_favorite">Gogokoak</string>
|
||||
<string name="notification_type_reblog">Bultzadak</string>
|
||||
@@ -229,7 +226,6 @@
|
||||
<string name="storage_permission_to_download">Aplikazioak zure biltegira sarbidea behar du fitxategia gordetzeko.</string>
|
||||
<string name="open_settings">Ireki ezarpenak</string>
|
||||
<string name="error_saving_file">Errorea fitxategia gordetzerakoan</string>
|
||||
<string name="file_saved">Fitxategia gorde da</string>
|
||||
<string name="downloading">Jeisten…</string>
|
||||
<string name="no_app_to_handle_action">Ez dago ekintza hau kudeatu dezkeen aplikaziorik</string>
|
||||
<string name="local_timeline">Zuzeneko jarioa</string>
|
||||
@@ -641,10 +637,8 @@
|
||||
Zenbat eta jende gehiago jarraitu, orduan eta aktiboagoa eta interesgarriagoa izango da.</string>
|
||||
<string name="onboarding_recommendations_title">Pertsonalizatu zure hasiera lerroa</string>
|
||||
<string name="article_by_author">%s(r)en eskutik</string>
|
||||
<string name="info">Informazioa</string>
|
||||
<string name="button_reblogged">Bultzatuta</string>
|
||||
<string name="button_favorited">Gogokoetara gehituta</string>
|
||||
<string name="bookmarked">Laster-marketara gehituta</string>
|
||||
<string name="join_server_x_with_invite">Batu %s -ra gonbidapenarekin</string>
|
||||
<string name="expired_invite_link">Iraungitako gonbidapen esteka</string>
|
||||
<string name="expired_clipboard_invite_link_alert">Zure arbeleko %1$s(e)rako gonbidapen-esteka iraungi egin da eta ezin da erregistratzeko erabili.\n\nLehendik dagoen erabiltzaile bati esteka berri bat eska diezaiokezu %2$s(e)ren bidez, edo erregistratzeko beste zerbitzari bat aukeratu.</string>
|
||||
@@ -774,6 +768,14 @@ Zenbat eta jende gehiago jarraitu, orduan eta aktiboagoa eta interesgarriagoa iz
|
||||
<item quantity="one">Erantsitako fitxategi %d</item>
|
||||
<item quantity="other">%d erantsitako fitxategi</item>
|
||||
</plurals>
|
||||
<plurals name="user_and_x_more_favorited">
|
||||
<item quantity="one">%1$s eta erabiltzaile %2$,d gehiagok zure bidalketa gogoko dute:</item>
|
||||
<item quantity="other">%1$s eta %2$,d erabiltzaile gehiagok zure bidalketa gogoko dute:</item>
|
||||
</plurals>
|
||||
<plurals name="user_and_x_more_boosted">
|
||||
<item quantity="one">%1$s eta erabiltzaile %2$,d gehiagok bultzada eman diote zure bidalketari:</item>
|
||||
<item quantity="other">%1$s eta %2$,d erabiltzaile gehiagok bultzada eman diote zure bidalketari:</item>
|
||||
</plurals>
|
||||
<plurals name="poll_ended_x_voters">
|
||||
<item quantity="one">%1$s erabiltzaileak inkesta bat egin zuen eta zuk eta beste erabiltzaile %2$,d bozkatu duzue</item>
|
||||
<item quantity="other">%1$s erabiltzaileak inkesta bat egin zuen eta zuk eta beste %2$,d erabiltzailek bozkatu duzue</item>
|
||||
|
||||
@@ -90,9 +90,7 @@
|
||||
</plurals>
|
||||
<string name="poll_closed">پایانیافته</string>
|
||||
<string name="do_mute">خموش</string>
|
||||
<string name="do_unmute">ناخموشی</string>
|
||||
<string name="do_block">مسدود کردن</string>
|
||||
<string name="do_unblock">رفع مسدودیت</string>
|
||||
<string name="button_blocked">مسدود شده</string>
|
||||
<string name="action_vote">رأی</string>
|
||||
<string name="delete">حذف</string>
|
||||
@@ -167,7 +165,6 @@
|
||||
<string name="visibility_followers_only">پیگیرندگان</string>
|
||||
<string name="visibility_private">افراد مشخّص</string>
|
||||
<string name="recent_searches">اخیر</string>
|
||||
<string name="skip">بعدی</string>
|
||||
<string name="notification_type_follow">پیگیرندگان جدید</string>
|
||||
<string name="notification_type_favorite">برگزیدهها</string>
|
||||
<string name="notification_type_reblog">تقویتها</string>
|
||||
@@ -229,7 +226,6 @@
|
||||
<string name="storage_permission_to_download">کاره برای ذخیره این پرونده نیازمند دسترسی به فضای ذخیرهسازی شما دارد.</string>
|
||||
<string name="open_settings">باز کردن تنظیمات</string>
|
||||
<string name="error_saving_file">خطا هنگام ذخیرهٔ کردن پرونده</string>
|
||||
<string name="file_saved">پرونده ذخیره شد</string>
|
||||
<string name="downloading">درحال بارگیری…</string>
|
||||
<string name="no_app_to_handle_action">هیچ کارهای برای رسیدگی به این اقدام وجود ندارد</string>
|
||||
<string name="local_timeline">خوراک زنده</string>
|
||||
@@ -635,10 +631,8 @@
|
||||
هرچه افراد بیشتری را پیگیری کنید، فعالتر و جالبتر خواهد بود.</string>
|
||||
<string name="onboarding_recommendations_title">خوراک خانه خود را شخصی سازی کنید</string>
|
||||
<string name="article_by_author">توسط %s</string>
|
||||
<string name="info">اطلاعات</string>
|
||||
<string name="button_reblogged">تقویت شد</string>
|
||||
<string name="button_favorited">برگزیده شد</string>
|
||||
<string name="bookmarked">نشانکگذاری شد</string>
|
||||
<string name="join_server_x_with_invite">پیوستن به %s با دعوت</string>
|
||||
<string name="expired_invite_link">منقضی کردن پیوند دعوت</string>
|
||||
<string name="invalid_invite_link">باطل کردن لینک دعوت</string>
|
||||
|
||||
@@ -84,9 +84,7 @@
|
||||
</plurals>
|
||||
<string name="poll_closed">Suljettu</string>
|
||||
<string name="do_mute">Mykistä</string>
|
||||
<string name="do_unmute">Poista mykistys</string>
|
||||
<string name="do_block">Estä</string>
|
||||
<string name="do_unblock">Poista esto</string>
|
||||
<string name="button_blocked">Estetty</string>
|
||||
<string name="action_vote">Äänestä</string>
|
||||
<string name="delete">Poista</string>
|
||||
@@ -156,7 +154,6 @@
|
||||
<string name="add_alt_text">Lisää selitys</string>
|
||||
<string name="visibility_public">Julkinen</string>
|
||||
<string name="recent_searches">Viimeisimmät</string>
|
||||
<string name="skip">Ohita</string>
|
||||
<string name="notification_type_follow">Uudet seuraajat</string>
|
||||
<string name="notification_type_favorite">Suosikit</string>
|
||||
<string name="notification_type_reblog">Tehostukset</string>
|
||||
@@ -215,7 +212,6 @@
|
||||
<string name="storage_permission_to_download">Sovellus tarvitsee pääsyn tallennustilaan, jotta voit tallentaa tämän tiedoston.</string>
|
||||
<string name="open_settings">Avaa asetukset</string>
|
||||
<string name="error_saving_file">Virhe tallennettaessa tiedostoa</string>
|
||||
<string name="file_saved">Tiedosto tallennettu</string>
|
||||
<string name="downloading">Ladataan…</string>
|
||||
<string name="no_app_to_handle_action">Tätä toimintoa käsittelevää sovellusta ei ole</string>
|
||||
<string name="trending_posts_info_banner">Nämä julkaisut ovat saamassa vetoa eri puolilla Mastodonia.</string>
|
||||
|
||||
@@ -76,9 +76,7 @@
|
||||
</plurals>
|
||||
<string name="poll_closed">Sarado</string>
|
||||
<string name="do_mute">I-Mute</string>
|
||||
<string name="do_unmute">I-unmute</string>
|
||||
<string name="do_block">I-Block</string>
|
||||
<string name="do_unblock">I-Unblock</string>
|
||||
<string name="button_blocked">Na-block</string>
|
||||
<string name="action_vote">Bumoto</string>
|
||||
<string name="delete">Tanggalin</string>
|
||||
@@ -123,7 +121,6 @@
|
||||
<string name="save">I-save</string>
|
||||
<string name="add_alt_text">Magdagdag ng teksto ng alt</string>
|
||||
<string name="visibility_public">Publiko</string>
|
||||
<string name="skip">Laktawan</string>
|
||||
<string name="notification_type_follow">Mga Bagong Follower</string>
|
||||
<string name="notification_type_favorite">Mga Paborito</string>
|
||||
<string name="notification_type_mention">Mga binangit</string>
|
||||
@@ -168,7 +165,6 @@
|
||||
<string name="storage_permission_to_download">Ang app ay nangangailangan ng access sa iyong storage para i-save ang file na ito.</string>
|
||||
<string name="open_settings">Buksan ang mga setting</string>
|
||||
<string name="error_saving_file">Error sa pag-save ng file</string>
|
||||
<string name="file_saved">Nai-save ang File</string>
|
||||
<string name="downloading">Nagda-download…</string>
|
||||
<!-- %s is the server domain -->
|
||||
<string name="load_missing_posts">Mag-Load ng nawawalang mga post</string>
|
||||
|
||||
@@ -88,9 +88,7 @@
|
||||
</plurals>
|
||||
<string name="poll_closed">Fermé</string>
|
||||
<string name="do_mute">Mettre en sourdine</string>
|
||||
<string name="do_unmute">Ne plus masquer</string>
|
||||
<string name="do_block">Bloquer</string>
|
||||
<string name="do_unblock">Débloquer</string>
|
||||
<string name="button_blocked">Bloqué</string>
|
||||
<string name="action_vote">Voter</string>
|
||||
<string name="delete">Supprimer</string>
|
||||
@@ -165,7 +163,6 @@
|
||||
<string name="visibility_followers_only">Abonné·e·s</string>
|
||||
<string name="visibility_private">Personnes spécifiques</string>
|
||||
<string name="recent_searches">Récents</string>
|
||||
<string name="skip">Passer</string>
|
||||
<string name="notification_type_follow">Nouveaux⋅elles abonné⋅e⋅s</string>
|
||||
<string name="notification_type_favorite">Favoris</string>
|
||||
<string name="notification_type_reblog">Partages</string>
|
||||
@@ -227,7 +224,6 @@
|
||||
<string name="storage_permission_to_download">L’application a besoin d’accéder à votre espace de stockage pour enregistrer ce fichier.</string>
|
||||
<string name="open_settings">Ouvrir les paramètres</string>
|
||||
<string name="error_saving_file">Erreur lors de l’enregistrement du fichier</string>
|
||||
<string name="file_saved">Fichier enregistré</string>
|
||||
<string name="downloading">Téléchargement…</string>
|
||||
<string name="no_app_to_handle_action">Aucune application ne permet de gérer cette action</string>
|
||||
<string name="local_timeline">Flux en direct</string>
|
||||
@@ -638,10 +634,8 @@
|
||||
<string name="onboarding_recommendations_intro">Vous organisez votre propre flux d\'accueil. Plus vous suivez de personnes, plus il sera actif et intéressant.</string>
|
||||
<string name="onboarding_recommendations_title">Personnaliser votre flux principal</string>
|
||||
<string name="article_by_author">Par %s</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="button_reblogged">Boosté</string>
|
||||
<string name="button_favorited">Ajouté au favoris</string>
|
||||
<string name="bookmarked">Ajouté aux signets</string>
|
||||
<string name="join_server_x_with_invite">Rejoindre %s avec invitation</string>
|
||||
<string name="expired_invite_link">Lien d\'invitation expiré</string>
|
||||
<string name="expired_clipboard_invite_link_alert">Le lien d\'invitation pour %1$s dans votre presse-papiers a expiré et ne peut pas être utilisé pour vous inscrire.\n\nVous pouvez demander un nouveau lien à un utilisateur existant, inscrivez-vous via %2$s, ou choisissez un autre serveur pour vous inscrire.</string>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user