Compare commits
53 Commits
v1.1.5+for
...
v1.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd582c4bee | ||
|
|
3a0d314af0 | ||
|
|
634408b8cb | ||
|
|
f050e3f22d | ||
|
|
8e9531b718 | ||
|
|
64fbbb2f07 | ||
|
|
5c2f72a706 | ||
|
|
b153a64373 | ||
|
|
1124486f1f | ||
|
|
bcb3e217cd | ||
|
|
a1798b6666 | ||
|
|
58ab0c0fc1 | ||
|
|
32a8d38edf | ||
|
|
a5c753a9f8 | ||
|
|
66cede567e | ||
|
|
c67b2b35f3 | ||
|
|
8588ca8ae3 | ||
|
|
7bb280e8b8 | ||
|
|
ddfeaabd44 | ||
|
|
51219bf98a | ||
|
|
512cb70347 | ||
|
|
6e718d6765 | ||
|
|
b26d491eda | ||
|
|
abdbab9d7b | ||
|
|
af1c7194e6 | ||
|
|
ab3a98fd60 | ||
|
|
6e6fdbccd5 | ||
|
|
1764e5f3d1 | ||
|
|
836c493951 | ||
|
|
d667b8fa98 | ||
|
|
6a1032cd61 | ||
|
|
557d535e5a | ||
|
|
60517b00f3 | ||
|
|
faf5e8e82b | ||
|
|
7264982761 | ||
|
|
fedf74258f | ||
|
|
def4960be6 | ||
|
|
525cc69c70 | ||
|
|
7ed1b164b5 | ||
|
|
a5fa44213d | ||
|
|
68e9d9d91c | ||
|
|
d9df150cf8 | ||
|
|
6f2e5a63d7 | ||
|
|
e0febda372 | ||
|
|
069d141451 | ||
|
|
166401ea18 | ||
|
|
b79ba71228 | ||
|
|
2903874dbc | ||
|
|
0dcdda75be | ||
|
|
202a5f9581 | ||
|
|
622c6d503d | ||
|
|
e10faeefc4 | ||
|
|
e9fe4a82df |
11
README.md
11
README.md
@@ -3,10 +3,17 @@ Mastodon for Android
|
||||
|
||||
[](https://crowdin.com/project/mastodon-for-android)
|
||||
|
||||
<a href="https://play.google.com/store/apps/details?id=org.joinmastodon.android"><img src="img/google-play-badge.png" height="50"></a>
|
||||
|
||||
This is the repository for the official Android app for Mastodon.
|
||||
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||
alt="Get it on F-Droid"
|
||||
height="80">](https://f-droid.org/packages/org.joinmastodon.android/)
|
||||
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png"
|
||||
alt="Get it on Google Play"
|
||||
height="80">](https://play.google.com/store/apps/details?id=org.joinmastodon.android)
|
||||
|
||||
Or get the APK from the [The Releases Section](https://github.com/mastodon/mastodon-android/releases/latest).
|
||||
|
||||
## Contributing
|
||||
|
||||
Our goal is delivering a polished, professionally designed and user-friendly app. We proceed according to wireframes provided by a professional UX designer that works with Mastodon gGmbH. This means that any outside contributions that change the app visually must first be coordinated with the UX designer. *This can take time.* Furthermore, we work off of an internal roadmap and aim for feature-parity and consistency with our iOS app. The iOS app is designated as the "primary" between the two, therefore, if you want to request features, please do so in the [Mastodon for iOS](https://github.com/mastodon/mastodon-ios) repository, as you are requesting a feature to be both in iOS and Android (exceptions being system integrations specific to Android). On the other hand, any contributions that improve existing functionality, performance, or accessibility should not have any roadblocks to being merged.
|
||||
|
||||
@@ -16,4 +16,4 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Automatically convert third-party libraries to use AndroidX
|
||||
android.enableJetifier=true
|
||||
android.enableJetifier=false
|
||||
@@ -9,13 +9,10 @@ android {
|
||||
applicationId "org.joinmastodon.android"
|
||||
minSdk 23
|
||||
targetSdk 33
|
||||
versionCode 45
|
||||
versionName "1.1.4"
|
||||
versionCode 50
|
||||
versionName "1.2.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
resConfigs "en", "ar-rSA", "bs-rBA", "ca-rES", "cs-rCZ", "de-rDE", "el-rGR", "es-rES",
|
||||
"eu-rES", "fi-rFI", "fr-rFR", "gl-rES", "hr-rHR", "hy-rAM", "it-rIT", "iw-rIL",
|
||||
"ja-rJP", "kab", "ko-rKR", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ru-rRU",
|
||||
"sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW"
|
||||
resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "da-rDK", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fa-rIR", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "ig-rNG", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "kab", "ko-rKR", "my-rMM", "nl-rNL", "no-rNO", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -91,4 +88,4 @@ dependencies {
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.4-alpha05'
|
||||
androidTestImplementation 'androidx.test:runner:1.5.0-alpha02'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0-alpha05'
|
||||
}
|
||||
}
|
||||
|
||||
4
mastodon/proguard-rules.pro
vendored
4
mastodon/proguard-rules.pro
vendored
@@ -44,6 +44,10 @@
|
||||
*;
|
||||
}
|
||||
|
||||
-keep interface org.parceler.Parcel
|
||||
-keep @org.parceler.Parcel class * { *; }
|
||||
-keep class **$$Parcelable { *; }
|
||||
|
||||
-keep class org.joinmastodon.android.AppCenterWrapper { *; }
|
||||
|
||||
-keepattributes LineNumberTable
|
||||
@@ -11,6 +11,13 @@
|
||||
|
||||
<permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT" />
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:name=".MastodonApp"
|
||||
android:allowBackup="true"
|
||||
|
||||
@@ -64,7 +64,7 @@ public class OAuthActivity extends Activity{
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Account account){
|
||||
AccountSessionManager.getInstance().addAccount(instance, token, account, app, true);
|
||||
AccountSessionManager.getInstance().addAccount(instance, token, account, app, null);
|
||||
progress.dismiss();
|
||||
finish();
|
||||
// not calling restartMainActivity() here on purpose to have it recreated (notice different flags)
|
||||
|
||||
@@ -162,6 +162,8 @@ public class PushSubscriptionManager{
|
||||
@Override
|
||||
public void onSuccess(PushSubscription result){
|
||||
MastodonAPIController.runInBackground(()->{
|
||||
result.serverKey=result.serverKey.replace('/','_');
|
||||
result.serverKey=result.serverKey.replace('+','-');
|
||||
serverKey=deserializeRawPublicKey(Base64.decode(result.serverKey, Base64.URL_SAFE));
|
||||
|
||||
AccountSession session=AccountSessionManager.getInstance().tryGetAccount(accountID);
|
||||
|
||||
@@ -9,7 +9,7 @@ public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscri
|
||||
Request r=new Request();
|
||||
r.subscription.endpoint="https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/"+accountID;
|
||||
r.data.alerts=alerts;
|
||||
r.data.policy=policy;
|
||||
r.policy=policy;
|
||||
r.subscription.keys.p256dh=encryptionKey;
|
||||
r.subscription.keys.auth=authKey;
|
||||
setRequestBody(r);
|
||||
@@ -18,6 +18,7 @@ public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscri
|
||||
private static class Request{
|
||||
public Subscription subscription=new Subscription();
|
||||
public Data data=new Data();
|
||||
public PushSubscription.Policy policy;
|
||||
|
||||
private static class Keys{
|
||||
public String p256dh;
|
||||
@@ -31,7 +32,6 @@ public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscri
|
||||
|
||||
private static class Data{
|
||||
public PushSubscription.Alerts alerts;
|
||||
public PushSubscription.Policy policy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,23 +3,36 @@ package org.joinmastodon.android.api.requests.notifications;
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.PushSubscription;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import okhttp3.Response;
|
||||
|
||||
public class UpdatePushSettings extends MastodonAPIRequest<PushSubscription>{
|
||||
private final PushSubscription.Policy policy;
|
||||
|
||||
public UpdatePushSettings(PushSubscription.Alerts alerts, PushSubscription.Policy policy){
|
||||
super(HttpMethod.PUT, "/push/subscription", PushSubscription.class);
|
||||
setRequestBody(new Request(alerts, policy));
|
||||
this.policy=policy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateAndPostprocessResponse(PushSubscription respObj, Response httpResponse) throws IOException{
|
||||
super.validateAndPostprocessResponse(respObj, httpResponse);
|
||||
respObj.policy=policy;
|
||||
}
|
||||
|
||||
private static class Request{
|
||||
public Data data=new Data();
|
||||
public PushSubscription.Policy policy;
|
||||
|
||||
public Request(PushSubscription.Alerts alerts, PushSubscription.Policy policy){
|
||||
this.data.alerts=alerts;
|
||||
this.data.policy=policy;
|
||||
this.policy=policy;
|
||||
}
|
||||
|
||||
private static class Data{
|
||||
public PushSubscription.Alerts alerts;
|
||||
public PushSubscription.Policy policy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,11 @@ import org.joinmastodon.android.model.Status;
|
||||
import java.util.List;
|
||||
|
||||
public class GetTrendingStatuses extends MastodonAPIRequest<List<Status>>{
|
||||
public GetTrendingStatuses(int limit){
|
||||
public GetTrendingStatuses(int offset, int limit){
|
||||
super(HttpMethod.GET, "/trends/statuses", new TypeToken<>(){});
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", ""+limit);
|
||||
if(offset>0)
|
||||
addQueryParameter("offset", ""+offset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.joinmastodon.android.api.session;
|
||||
|
||||
public class AccountActivationInfo{
|
||||
public String email;
|
||||
public long lastEmailConfirmationResend;
|
||||
|
||||
public AccountActivationInfo(String email, long lastEmailConfirmationResend){
|
||||
this.email=email;
|
||||
this.lastEmailConfirmationResend=lastEmailConfirmationResend;
|
||||
}
|
||||
}
|
||||
@@ -28,17 +28,19 @@ public class AccountSession{
|
||||
public long filtersLastUpdated;
|
||||
public List<Filter> wordFilters=new ArrayList<>();
|
||||
public String pushAccountID;
|
||||
public AccountActivationInfo activationInfo;
|
||||
private transient MastodonAPIController apiController;
|
||||
private transient StatusInteractionController statusInteractionController;
|
||||
private transient CacheController cacheController;
|
||||
private transient PushSubscriptionManager pushSubscriptionManager;
|
||||
|
||||
AccountSession(Token token, Account self, Application app, String domain, boolean activated){
|
||||
AccountSession(Token token, Account self, Application app, String domain, boolean activated, AccountActivationInfo activationInfo){
|
||||
this.token=token;
|
||||
this.self=self;
|
||||
this.domain=domain;
|
||||
this.app=app;
|
||||
this.activated=activated;
|
||||
this.activationInfo=activationInfo;
|
||||
infoLastUpdated=System.currentTimeMillis();
|
||||
}
|
||||
|
||||
|
||||
@@ -100,9 +100,9 @@ public class AccountSessionManager{
|
||||
maybeUpdateShortcuts();
|
||||
}
|
||||
|
||||
public void addAccount(Instance instance, Token token, Account self, Application app, boolean active){
|
||||
public void addAccount(Instance instance, Token token, Account self, Application app, AccountActivationInfo activationInfo){
|
||||
instances.put(instance.uri, instance);
|
||||
AccountSession session=new AccountSession(token, self, app, instance.uri, active);
|
||||
AccountSession session=new AccountSession(token, self, app, instance.uri, activationInfo==null, activationInfo);
|
||||
sessions.put(session.getID(), session);
|
||||
lastActiveAccountID=session.getID();
|
||||
writeAccountsFile();
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import static android.os.ext.SdkExtensions.getExtensionVersion;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
@@ -20,6 +22,7 @@ import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.text.Editable;
|
||||
import android.text.InputFilter;
|
||||
@@ -211,7 +214,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
else
|
||||
charLimit=500;
|
||||
|
||||
loadDefaultStatusVisibility(savedInstanceState);
|
||||
if (editingStatus == null) loadDefaultStatusVisibility(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -330,6 +333,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
spoilerBg.setDrawableByLayerId(R.id.right_drawable, new SpoilerStripesDrawable());
|
||||
spoilerEdit.setBackground(spoilerBg);
|
||||
if((savedInstanceState!=null && savedInstanceState.getBoolean("hasSpoiler", false)) || hasSpoiler){
|
||||
hasSpoiler=true;
|
||||
spoilerEdit.setVisibility(View.VISIBLE);
|
||||
spoilerBtn.setSelected(true);
|
||||
}else if(editingStatus!=null && !TextUtils.isEmpty(editingStatus.spoilerText)){
|
||||
@@ -410,6 +414,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
|
||||
mainEditText.setSelectionListener(this);
|
||||
mainEditText.addTextChangedListener(new TextWatcher(){
|
||||
private int lastChangeStart, lastChangeCount;
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after){
|
||||
|
||||
@@ -419,6 +425,16 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count){
|
||||
if(s.length()==0)
|
||||
return;
|
||||
lastChangeStart=start;
|
||||
lastChangeCount=count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s){
|
||||
if(s.length()==0)
|
||||
return;
|
||||
int start=lastChangeStart;
|
||||
int count=lastChangeCount;
|
||||
// offset one char back to catch an already typed '@' or '#' or ':'
|
||||
int realStart=start;
|
||||
start=Math.max(0, start-1);
|
||||
@@ -464,10 +480,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
editable.removeSpan(span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s){
|
||||
updateCharCounter();
|
||||
}
|
||||
});
|
||||
@@ -514,7 +527,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
DraftMediaAttachment da=new DraftMediaAttachment();
|
||||
da.serverAttachment=att;
|
||||
da.description=att.description;
|
||||
da.uri=Uri.parse(att.previewUrl);
|
||||
da.uri=att.previewUrl!=null ? Uri.parse(att.previewUrl) : null;
|
||||
da.state=AttachmentUploadState.DONE;
|
||||
attachmentsView.addView(createMediaAttachmentView(da));
|
||||
attachments.add(da);
|
||||
@@ -782,14 +795,38 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
.show();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builds the correct intent for the device version to select media.
|
||||
*
|
||||
* <p>For Device version > T or R_SDK_v2, use the android platform photopicker via
|
||||
* {@link MediaStore#ACTION_PICK_IMAGES}
|
||||
*
|
||||
* <p>For earlier versions use the built in docs ui via {@link Intent#ACTION_GET_CONTENT}
|
||||
*/
|
||||
private void openFilePicker(){
|
||||
Intent intent=new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("*/*");
|
||||
if(instance.configuration!=null && instance.configuration.mediaAttachments!=null && instance.configuration.mediaAttachments.supportedMimeTypes!=null && !instance.configuration.mediaAttachments.supportedMimeTypes.isEmpty()){
|
||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, instance.configuration.mediaAttachments.supportedMimeTypes.toArray(new String[0]));
|
||||
Intent intent;
|
||||
boolean usePhotoPicker=UiUtils.isPhotoPickerAvailable();
|
||||
if(usePhotoPicker){
|
||||
intent=new Intent(MediaStore.ACTION_PICK_IMAGES);
|
||||
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MAX_ATTACHMENTS-getMediaAttachmentsCount());
|
||||
}else{
|
||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/*", "video/*"});
|
||||
intent=new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("*/*");
|
||||
}
|
||||
if(!usePhotoPicker && instance.configuration!=null &&
|
||||
instance.configuration.mediaAttachments!=null &&
|
||||
instance.configuration.mediaAttachments.supportedMimeTypes!=null &&
|
||||
!instance.configuration.mediaAttachments.supportedMimeTypes.isEmpty()){
|
||||
intent.putExtra(Intent.EXTRA_MIME_TYPES,
|
||||
instance.configuration.mediaAttachments.supportedMimeTypes.toArray(
|
||||
new String[0]));
|
||||
}else{
|
||||
if(!usePhotoPicker){
|
||||
// If photo picker is being used these are the default mimetypes.
|
||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/*", "video/*"});
|
||||
}
|
||||
}
|
||||
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
|
||||
startActivityForResult(intent, MEDIA_RESULT);
|
||||
@@ -871,7 +908,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
View thumb=getActivity().getLayoutInflater().inflate(R.layout.compose_media_thumb, attachmentsView, false);
|
||||
ImageView img=thumb.findViewById(R.id.thumb);
|
||||
if(draft.serverAttachment!=null){
|
||||
ViewImageLoader.load(img, draft.serverAttachment.blurhashPlaceholder, new UrlImageLoaderRequest(draft.serverAttachment.previewUrl, V.dp(250), V.dp(250)));
|
||||
if(draft.serverAttachment.previewUrl!=null)
|
||||
ViewImageLoader.load(img, draft.serverAttachment.blurhashPlaceholder, new UrlImageLoaderRequest(draft.serverAttachment.previewUrl, V.dp(250), V.dp(250)));
|
||||
}else{
|
||||
if(draft.mimeType.startsWith("image/")){
|
||||
ViewImageLoader.load(img, null, new UrlImageLoaderRequest(draft.uri, V.dp(250), V.dp(250)));
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.discover.DiscoverFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.OnboardingFollowSuggestionsFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
@@ -31,6 +32,7 @@ import java.util.ArrayList;
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.Nullable;
|
||||
import me.grishka.appkit.FragmentStackActivity;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.fragments.AppKitFragment;
|
||||
import me.grishka.appkit.fragments.LoaderFragment;
|
||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
@@ -235,6 +237,11 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
new AccountSwitcherSheet(getActivity()).show();
|
||||
return true;
|
||||
}
|
||||
if(tab==R.id.tab_home){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.go(getActivity(), OnboardingFollowSuggestionsFragment.class, args);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
||||
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||
@@ -61,6 +62,7 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||
private AnimatorSet currentNewPostsAnim;
|
||||
|
||||
private String maxID;
|
||||
private String lastSavedMarkerID;
|
||||
|
||||
public HomeTimelineFragment(){
|
||||
setListLayoutId(R.layout.recycler_fragment_with_fab);
|
||||
@@ -142,6 +144,29 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHidden(){
|
||||
super.onHidden();
|
||||
if(!data.isEmpty()){
|
||||
String topPostID=displayItems.get(Math.max(0, list.getChildAdapterPosition(list.getChildAt(0))-getMainAdapterOffset())).parentID;
|
||||
if(!topPostID.equals(lastSavedMarkerID)){
|
||||
lastSavedMarkerID=topPostID;
|
||||
new SaveMarkers(topPostID, null)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(SaveMarkers.Response result){
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
lastSavedMarkerID=null;
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onStatusCreated(StatusCreatedEvent ev){
|
||||
prependItems(Collections.singletonList(ev.status), true);
|
||||
}
|
||||
|
||||
@@ -72,6 +72,18 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
||||
|
||||
tabLayout.setTabTextSize(V.dp(16));
|
||||
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
||||
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
||||
@Override
|
||||
public void onTabSelected(TabLayout.Tab tab) {}
|
||||
|
||||
@Override
|
||||
public void onTabUnselected(TabLayout.Tab tab) {}
|
||||
|
||||
@Override
|
||||
public void onTabReselected(TabLayout.Tab tab) {
|
||||
scrollToTop();
|
||||
}
|
||||
});
|
||||
|
||||
pager.setOffscreenPageLimit(4);
|
||||
pager.setAdapter(new DiscoverPagerAdapter());
|
||||
@@ -137,6 +149,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
||||
protected void updateToolbar(){
|
||||
super.updateToolbar();
|
||||
getToolbar().setOutlineProvider(null);
|
||||
getToolbar().setOnClickListener(v->scrollToTop());
|
||||
}
|
||||
|
||||
private NotificationsListFragment getFragmentForPage(int page){
|
||||
|
||||
@@ -281,7 +281,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
username+="@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
|
||||
}
|
||||
getActivity().getSystemService(ClipboardManager.class).setPrimaryClip(ClipData.newPlainText(null, "@"+username));
|
||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.TIRAMISU){ // Android 13+ SystemUI shows its own thing when you put things into the clipboard
|
||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.TIRAMISU || UiUtils.isMIUI()){ // Android 13+ SystemUI shows its own thing when you put things into the clipboard
|
||||
Toast.makeText(getActivity(), R.string.text_copied, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -37,9 +37,11 @@ import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.api.PushSubscriptionManager;
|
||||
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
|
||||
import org.joinmastodon.android.api.session.AccountActivationInfo;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
||||
import org.joinmastodon.android.model.PushNotification;
|
||||
import org.joinmastodon.android.model.PushSubscription;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
@@ -55,6 +57,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.imageloader.ImageCache;
|
||||
@@ -122,6 +125,19 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
items.add(new TextItem(R.string.settings_clear_cache, this::clearImageCache));
|
||||
items.add(new TextItem(R.string.log_out, this::confirmLogOut));
|
||||
|
||||
if(BuildConfig.DEBUG){
|
||||
items.add(new RedHeaderItem("Debug options"));
|
||||
items.add(new TextItem("Test e-mail confirmation flow", ()->{
|
||||
AccountSession sess=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
sess.activated=false;
|
||||
sess.activationInfo=new AccountActivationInfo("test@email", System.currentTimeMillis());
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putBoolean("debug", true);
|
||||
Nav.goClearingStack(getActivity(), AccountActivationFragment.class, args);
|
||||
}));
|
||||
}
|
||||
|
||||
items.add(new FooterItem(getString(R.string.settings_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)));
|
||||
}
|
||||
|
||||
@@ -346,6 +362,10 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
this.text=getString(text);
|
||||
}
|
||||
|
||||
public HeaderItem(String text){
|
||||
this.text=text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewType(){
|
||||
return 0;
|
||||
@@ -405,6 +425,11 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
this.onClick=onClick;
|
||||
}
|
||||
|
||||
public TextItem(String text, Runnable onClick){
|
||||
this.text=text;
|
||||
this.onClick=onClick;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewType(){
|
||||
return 4;
|
||||
@@ -417,6 +442,10 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
super(text);
|
||||
}
|
||||
|
||||
public RedHeaderItem(String text){
|
||||
super(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewType(){
|
||||
return 5;
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.SpannableString;
|
||||
import android.text.style.ReplacementSpan;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.onboarding.InstanceCatalogSignupFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.InstanceChooserLoginFragment;
|
||||
import org.joinmastodon.android.ui.InterpolatingMotionEffect;
|
||||
import org.joinmastodon.android.ui.views.SizeListenerFrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.fragments.AppKitFragment;
|
||||
import me.grishka.appkit.utils.V;
|
||||
@@ -23,12 +31,13 @@ public class SplashFragment extends AppKitFragment{
|
||||
|
||||
private SizeListenerFrameLayout contentView;
|
||||
private View artContainer, blueFill, greenFill;
|
||||
private InterpolatingMotionEffect motionEffect;
|
||||
private ViewPager2 pager;
|
||||
private ViewGroup pagerDots;
|
||||
private View artClouds, artPlaneElephant, artRightHill, artLeftHill, artCenterHill;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
motionEffect=new InterpolatingMotionEffect(MastodonApp.context);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -37,15 +46,44 @@ public class SplashFragment extends AppKitFragment{
|
||||
contentView=(SizeListenerFrameLayout) inflater.inflate(R.layout.fragment_splash, container, false);
|
||||
contentView.findViewById(R.id.btn_get_started).setOnClickListener(this::onButtonClick);
|
||||
contentView.findViewById(R.id.btn_log_in).setOnClickListener(this::onButtonClick);
|
||||
artClouds=contentView.findViewById(R.id.art_clouds);
|
||||
artPlaneElephant=contentView.findViewById(R.id.art_plane_elephant);
|
||||
artRightHill=contentView.findViewById(R.id.art_right_hill);
|
||||
artLeftHill=contentView.findViewById(R.id.art_left_hill);
|
||||
artCenterHill=contentView.findViewById(R.id.art_center_hill);
|
||||
pager=contentView.findViewById(R.id.pager);
|
||||
pagerDots=contentView.findViewById(R.id.pager_dots);
|
||||
pager.setAdapter(new PagerAdapter());
|
||||
pager.setOffscreenPageLimit(3);
|
||||
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels){
|
||||
for(int i=0;i<pagerDots.getChildCount();i++){
|
||||
float alpha;
|
||||
if(i==position){
|
||||
alpha=0.3f+0.7f*(1f-positionOffset);
|
||||
}else if(i==position+1){
|
||||
alpha=0.3f+0.7f*positionOffset;
|
||||
}else{
|
||||
alpha=0.3f;
|
||||
}
|
||||
pagerDots.getChildAt(i).setAlpha(alpha);
|
||||
}
|
||||
|
||||
float parallaxProgress=(position+positionOffset)/2f;
|
||||
artClouds.setTranslationX(V.dp(-27)*(position>=1 ? 1f : positionOffset));
|
||||
artPlaneElephant.setTranslationX(V.dp(101.55f)*parallaxProgress);
|
||||
artLeftHill.setTranslationX(V.dp(-88)*parallaxProgress);
|
||||
artLeftHill.setTranslationY(V.dp(24)*parallaxProgress);
|
||||
artRightHill.setTranslationX(V.dp(-88)*parallaxProgress);
|
||||
artRightHill.setTranslationY(V.dp(-24)*parallaxProgress);
|
||||
artCenterHill.setTranslationX(V.dp(-40)*parallaxProgress);
|
||||
}
|
||||
});
|
||||
|
||||
artContainer=contentView.findViewById(R.id.art_container);
|
||||
blueFill=contentView.findViewById(R.id.blue_fill);
|
||||
greenFill=contentView.findViewById(R.id.green_fill);
|
||||
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_clouds), V.dp(-5), V.dp(5), V.dp(-5), V.dp(5)));
|
||||
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_right_hill), V.dp(-15), V.dp(25), V.dp(-10), V.dp(10)));
|
||||
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_left_hill), V.dp(-25), V.dp(15), V.dp(-15), V.dp(15)));
|
||||
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_center_hill), V.dp(-14), V.dp(14), V.dp(-5), V.dp(25)));
|
||||
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_plane_elephant), V.dp(-20), V.dp(12), V.dp(-20), V.dp(12)));
|
||||
|
||||
contentView.setSizeListener(new SizeListenerFrameLayout.OnSizeChangedListener(){
|
||||
@Override
|
||||
@@ -72,10 +110,10 @@ public class SplashFragment extends AppKitFragment{
|
||||
}
|
||||
|
||||
private void updateArtSize(int w, int h){
|
||||
float scale=w/(float)V.dp(412);
|
||||
float scale=w/(float)V.dp(360);
|
||||
artContainer.setScaleX(scale);
|
||||
artContainer.setScaleY(scale);
|
||||
blueFill.setScaleY(h/2f);
|
||||
blueFill.setScaleY(artContainer.getBottom()-V.dp(90));
|
||||
greenFill.setScaleY(h-artContainer.getBottom()+V.dp(90));
|
||||
}
|
||||
|
||||
@@ -101,15 +139,60 @@ public class SplashFragment extends AppKitFragment{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShown(){
|
||||
super.onShown();
|
||||
motionEffect.activate();
|
||||
private class PagerAdapter extends RecyclerView.Adapter<PagerViewHolder>{
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public PagerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new PagerViewHolder(viewType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull PagerViewHolder holder, int position){}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position){
|
||||
return position;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHidden(){
|
||||
super.onHidden();
|
||||
motionEffect.deactivate();
|
||||
private class PagerViewHolder extends RecyclerView.ViewHolder{
|
||||
public PagerViewHolder(int page){
|
||||
super(new LinearLayout(getActivity()));
|
||||
LinearLayout ll=(LinearLayout) itemView;
|
||||
ll.setOrientation(LinearLayout.VERTICAL);
|
||||
int pad=V.dp(16);
|
||||
ll.setPadding(pad, pad, pad, pad);
|
||||
ll.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
TextView title=new TextView(getActivity());
|
||||
title.setTextAppearance(R.style.m3_headline_medium);
|
||||
title.setText(switch(page){
|
||||
case 0 -> getString(R.string.welcome_page1_title);
|
||||
case 1 -> getString(R.string.welcome_page2_title);
|
||||
case 2 -> getString(R.string.welcome_page3_title);
|
||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
||||
});
|
||||
title.setTextColor(0xFF17063B);
|
||||
LinearLayout.LayoutParams lp=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(page==0 ? 46 : 36));
|
||||
lp.bottomMargin=V.dp(page==0 ? 4 : 14);
|
||||
ll.addView(title, lp);
|
||||
|
||||
TextView text=new TextView(getActivity());
|
||||
text.setTextAppearance(R.style.m3_body_medium);
|
||||
text.setText(switch(page){
|
||||
case 0 -> R.string.welcome_page1_text;
|
||||
case 1 -> R.string.welcome_page2_text;
|
||||
case 2 -> R.string.welcome_page3_text;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
||||
});
|
||||
text.setTextColor(0xFF17063B);
|
||||
ll.addView(text, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@ public class DiscoverPostsFragment extends StatusListFragment{
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetTrendingStatuses(count)
|
||||
currentRequest=new GetTrendingStatuses(offset, count)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
onDataLoaded(result, false);
|
||||
onDataLoaded(result, !result.isEmpty());
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
@@ -12,19 +12,21 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
||||
import org.joinmastodon.android.api.requests.accounts.ResendConfirmationEmail;
|
||||
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
|
||||
import org.joinmastodon.android.api.session.AccountActivationInfo;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.HomeFragment;
|
||||
import org.joinmastodon.android.fragments.SettingsFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.io.File;
|
||||
@@ -35,40 +37,50 @@ import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.APIRequest;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.AppKitFragment;
|
||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class AccountActivationFragment extends AppKitFragment{
|
||||
public class AccountActivationFragment extends ToolbarFragment{
|
||||
private String accountID;
|
||||
|
||||
private Button btn, backBtn;
|
||||
private View buttonBar;
|
||||
private Button openEmailBtn, resendBtn;
|
||||
private View contentView;
|
||||
private Handler uiHandler=new Handler(Looper.getMainLooper());
|
||||
private Runnable pollRunnable=this::tryGetAccount;
|
||||
private APIRequest currentRequest;
|
||||
private Runnable resendTimer=this::updateResendTimer;
|
||||
private long lastResendTime;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
accountID=getArguments().getString("account");
|
||||
setTitle(R.string.confirm_email_title);
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
lastResendTime=session.activationInfo!=null ? session.activationInfo.lastEmailConfirmationResend : 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
|
||||
public View onCreateContentView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
|
||||
View view=inflater.inflate(R.layout.fragment_onboarding_activation, container, false);
|
||||
|
||||
btn=view.findViewById(R.id.btn_next);
|
||||
btn.setOnClickListener(v->onButtonClick());
|
||||
btn.setOnLongClickListener(v->{
|
||||
openEmailBtn=view.findViewById(R.id.btn_next);
|
||||
openEmailBtn.setOnClickListener(this::onOpenEmailClick);
|
||||
openEmailBtn.setOnLongClickListener(v->{
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.go(getActivity(), SettingsFragment.class, args);
|
||||
return true;
|
||||
});
|
||||
buttonBar=view.findViewById(R.id.button_bar);
|
||||
view.findViewById(R.id.btn_back).setOnClickListener(v->onBackButtonClick());
|
||||
resendBtn=view.findViewById(R.id.btn_resend);
|
||||
resendBtn.setOnClickListener(this::onResendClick);
|
||||
TextView text=view.findViewById(R.id.subtitle);
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
text.setText(getString(R.string.confirm_email_subtitle, session.activationInfo!=null ? session.activationInfo.email : "?"));
|
||||
updateResendTimer();
|
||||
|
||||
contentView=view;
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -80,14 +92,32 @@ public class AccountActivationFragment extends AppKitFragment{
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUpdateToolbar(){
|
||||
super.onUpdateToolbar();
|
||||
getToolbar().setBackground(null);
|
||||
getToolbar().setElevation(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canGoBack(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onToolbarNavigationClick(){
|
||||
new AccountSwitcherSheet(getActivity()).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyWindowInsets(WindowInsets insets){
|
||||
if(Build.VERSION.SDK_INT>=27){
|
||||
int inset=insets.getSystemWindowInsetBottom();
|
||||
buttonBar.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0);
|
||||
contentView.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0);
|
||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), 0));
|
||||
}else{
|
||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
||||
@@ -111,7 +141,7 @@ public class AccountActivationFragment extends AppKitFragment{
|
||||
}
|
||||
}
|
||||
|
||||
private void onButtonClick(){
|
||||
private void onOpenEmailClick(View v){
|
||||
try{
|
||||
startActivity(Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_EMAIL).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
}catch(ActivityNotFoundException x){
|
||||
@@ -119,12 +149,21 @@ public class AccountActivationFragment extends AppKitFragment{
|
||||
}
|
||||
}
|
||||
|
||||
private void onBackButtonClick(){
|
||||
private void onResendClick(View v){
|
||||
new ResendConfirmationEmail(null)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Object result){
|
||||
Toast.makeText(getActivity(), R.string.resent_email, Toast.LENGTH_SHORT).show();
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
if(session.activationInfo==null){
|
||||
session.activationInfo=new AccountActivationInfo("?", System.currentTimeMillis());
|
||||
}else{
|
||||
session.activationInfo.lastEmailConfirmationResend=System.currentTimeMillis();
|
||||
}
|
||||
lastResendTime=session.activationInfo.lastEmailConfirmationResend;
|
||||
AccountSessionManager.getInstance().writeAccountsFile();
|
||||
updateResendTimer();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -152,32 +191,26 @@ public class AccountActivationFragment extends AppKitFragment{
|
||||
AccountSessionManager mgr=AccountSessionManager.getInstance();
|
||||
AccountSession session=mgr.getAccount(accountID);
|
||||
mgr.removeAccount(accountID);
|
||||
mgr.addAccount(mgr.getInstanceInfo(session.domain), session.token, result, session.app, true);
|
||||
mgr.addAccount(mgr.getInstanceInfo(session.domain), session.token, result, session.app, null);
|
||||
String newID=mgr.getLastActiveAccountID();
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", newID);
|
||||
if(session.self.avatar!=null || session.self.displayName!=null){
|
||||
File avaFile=session.self.avatar!=null ? new File(session.self.avatar) : null;
|
||||
new UpdateAccountCredentials(session.self.displayName, "", avaFile, null, Collections.emptyList())
|
||||
accountID=newID;
|
||||
if((session.self.avatar!=null || session.self.displayName!=null) && !getArguments().getBoolean("debug")){
|
||||
new UpdateAccountCredentials(session.self.displayName, "", (File)null, null, Collections.emptyList())
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Account result){
|
||||
if(avaFile!=null)
|
||||
avaFile.delete();
|
||||
mgr.updateAccountInfo(newID, result);
|
||||
Nav.goClearingStack(getActivity(), HomeFragment.class, args);
|
||||
proceed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
if(avaFile!=null)
|
||||
avaFile.delete();
|
||||
Nav.goClearingStack(getActivity(), HomeFragment.class, args);
|
||||
proceed();
|
||||
}
|
||||
})
|
||||
.exec(newID);
|
||||
}else{
|
||||
Nav.goClearingStack(getActivity(), HomeFragment.class, args);
|
||||
proceed();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,4 +222,32 @@ public class AccountActivationFragment extends AppKitFragment{
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
private void updateResendTimer(){
|
||||
long sinceResend=System.currentTimeMillis()-lastResendTime;
|
||||
if(sinceResend>59_000L){
|
||||
resendBtn.setText(R.string.resend);
|
||||
resendBtn.setEnabled(true);
|
||||
return;
|
||||
}
|
||||
int seconds=(int)((60_000L-sinceResend)/1000L);
|
||||
resendBtn.setText(String.format("%s (%d)", getString(R.string.resend), seconds));
|
||||
if(resendBtn.isEnabled())
|
||||
resendBtn.setEnabled(false);
|
||||
resendBtn.postDelayed(resendTimer, 500);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView(){
|
||||
super.onDestroyView();
|
||||
resendBtn.removeCallbacks(resendTimer);
|
||||
}
|
||||
|
||||
private void proceed(){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
// Nav.goClearingStack(getActivity(), HomeFragment.class, args);
|
||||
Nav.goClearingStack(getActivity(), OnboardingFollowSuggestionsFragment.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -15,8 +17,10 @@ import android.widget.TextView;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.parceler.Parcels;
|
||||
@@ -33,12 +37,14 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.fragments.AppKitFragment;
|
||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
@@ -46,7 +52,7 @@ import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
public class GoogleMadeMeAddThisFragment extends AppKitFragment{
|
||||
public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
||||
private UsableRecyclerView list;
|
||||
private MergeRecyclerAdapter adapter;
|
||||
private Button btn;
|
||||
@@ -55,11 +61,15 @@ public class GoogleMadeMeAddThisFragment extends AppKitFragment{
|
||||
private ArrayList<Item> items=new ArrayList<>();
|
||||
private Call currentRequest;
|
||||
private ItemsAdapter itemsAdapter;
|
||||
private ElevationOnScrollListener onScrollListener;
|
||||
|
||||
private static final int SIGNUP_REQUEST=722;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
setRetainInstance(true);
|
||||
setTitle(R.string.privacy_policy_title);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -68,7 +78,7 @@ public class GoogleMadeMeAddThisFragment extends AppKitFragment{
|
||||
setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground));
|
||||
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
|
||||
|
||||
items.add(new Item("Mastodon for Android Privacy Policy", "joinmastodon.org", "https://joinmastodon.org/android/privacy", "https://joinmastodon.org/favicon-32x32.png"));
|
||||
items.add(new Item("Mastodon for Android Privacy Policy", getString(R.string.privacy_policy_explanation), "joinmastodon.org", "https://joinmastodon.org/android/privacy", "https://joinmastodon.org/favicon-32x32.png"));
|
||||
loadServerPrivacyPolicy();
|
||||
}
|
||||
|
||||
@@ -82,37 +92,30 @@ public class GoogleMadeMeAddThisFragment extends AppKitFragment{
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||
View view=inflater.inflate(R.layout.fragment_onboarding_rules, container, false);
|
||||
|
||||
list=view.findViewById(R.id.list);
|
||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
View headerView=inflater.inflate(R.layout.item_list_header, list, false);
|
||||
TextView title=headerView.findViewById(R.id.title);
|
||||
TextView subtitle=headerView.findViewById(R.id.subtitle);
|
||||
headerView.findViewById(R.id.step_counter).setVisibility(View.GONE);
|
||||
title.setText(R.string.privacy_policy_title);
|
||||
subtitle.setText(R.string.privacy_policy_subtitle);
|
||||
View headerView=inflater.inflate(R.layout.item_list_header_simple, list, false);
|
||||
TextView text=headerView.findViewById(R.id.text);
|
||||
text.setText(getString(R.string.privacy_policy_subtitle, instance.uri));
|
||||
|
||||
adapter=new MergeRecyclerAdapter();
|
||||
adapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
||||
adapter.addAdapter(itemsAdapter=new ItemsAdapter());
|
||||
list.setAdapter(adapter);
|
||||
list.setSelector(null);
|
||||
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
if(parent.getChildViewHolder(view) instanceof ItemViewHolder){
|
||||
outRect.left=outRect.right=V.dp(18.5f);
|
||||
outRect.top=V.dp(16);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
btn=view.findViewById(R.id.btn_next);
|
||||
btn.setOnClickListener(v->onButtonClick());
|
||||
buttonBar=view.findViewById(R.id.button_bar);
|
||||
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
|
||||
|
||||
Button backBtn=view.findViewById(R.id.btn_back);
|
||||
backBtn.setText(getString(R.string.server_policy_disagree, instance.uri));
|
||||
backBtn.setOnClickListener(v->{
|
||||
setResult(false, null);
|
||||
Nav.finish(this);
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -120,13 +123,34 @@ public class GoogleMadeMeAddThisFragment extends AppKitFragment{
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
list.addOnScrollListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUpdateToolbar(){
|
||||
super.onUpdateToolbar();
|
||||
getToolbar().setBackgroundResource(R.drawable.bg_onboarding_panel);
|
||||
getToolbar().setElevation(0);
|
||||
if(onScrollListener!=null){
|
||||
onScrollListener.setViews(buttonBar, getToolbar());
|
||||
}
|
||||
}
|
||||
|
||||
protected void onButtonClick(){
|
||||
Bundle args=new Bundle();
|
||||
args.putParcelable("instance", Parcels.wrap(instance));
|
||||
Nav.go(getActivity(), SignupFragment.class, args);
|
||||
Nav.goForResult(getActivity(), SignupFragment.class, args, SIGNUP_REQUEST, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFragmentResult(int reqCode, boolean success, Bundle result){
|
||||
super.onFragmentResult(reqCode, success, result);
|
||||
if(reqCode==SIGNUP_REQUEST && !success){
|
||||
setResult(false, null);
|
||||
Nav.finish(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -159,7 +183,7 @@ public class GoogleMadeMeAddThisFragment extends AppKitFragment{
|
||||
if(!response.isSuccessful())
|
||||
return;
|
||||
Document doc=Jsoup.parse(Objects.requireNonNull(body).byteStream(), Objects.requireNonNull(body.contentType()).charset(StandardCharsets.UTF_8).name(), req.url().toString());
|
||||
final Item item=new Item(doc.title(), instance.uri, req.url().toString(), "https://"+instance.uri+"/favicon.ico");
|
||||
final Item item=new Item(doc.title(), null, instance.uri, req.url().toString(), "https://"+instance.uri+"/favicon.ico");
|
||||
Activity activity=getActivity();
|
||||
if(activity!=null){
|
||||
activity.runOnUiThread(()->{
|
||||
@@ -192,24 +216,24 @@ public class GoogleMadeMeAddThisFragment extends AppKitFragment{
|
||||
}
|
||||
|
||||
private class ItemViewHolder extends BindableViewHolder<Item> implements UsableRecyclerView.Clickable{
|
||||
private final TextView domain, title;
|
||||
private final ImageView favicon;
|
||||
private final TextView title;
|
||||
private final TextView subtitle;
|
||||
|
||||
public ItemViewHolder(){
|
||||
super(getActivity(), R.layout.item_privacy_policy_link, list);
|
||||
domain=findViewById(R.id.domain);
|
||||
title=findViewById(R.id.title);
|
||||
favicon=findViewById(R.id.favicon);
|
||||
itemView.setOutlineProvider(OutlineProviders.roundedRect(10));
|
||||
itemView.setClipToOutline(true);
|
||||
subtitle=findViewById(R.id.subtitle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(Item item){
|
||||
domain.setText(item.domain);
|
||||
title.setText(item.title);
|
||||
|
||||
ViewImageLoader.load(favicon, null, new UrlImageLoaderRequest(item.faviconUrl));
|
||||
if(TextUtils.isEmpty(item.subtitle)){
|
||||
subtitle.setVisibility(View.GONE);
|
||||
}else{
|
||||
subtitle.setVisibility(View.VISIBLE);
|
||||
subtitle.setText(item.subtitle);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -219,10 +243,11 @@ public class GoogleMadeMeAddThisFragment extends AppKitFragment{
|
||||
}
|
||||
|
||||
private static class Item{
|
||||
public String title, domain, url, faviconUrl;
|
||||
public String title, subtitle, domain, url, faviconUrl;
|
||||
|
||||
public Item(String title, String domain, String url, String faviconUrl){
|
||||
public Item(String title, String subtitle, String domain, String url, String faviconUrl){
|
||||
this.title=title;
|
||||
this.subtitle=subtitle;
|
||||
this.domain=domain;
|
||||
this.url=url;
|
||||
this.faviconUrl=faviconUrl;
|
||||
|
||||
@@ -5,15 +5,12 @@ import android.app.ProgressDialog;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.LocaleList;
|
||||
import android.text.TextUtils;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
@@ -23,7 +20,6 @@ import org.joinmastodon.android.api.requests.instance.GetInstance;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.catalog.CatalogInstance;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NodeList;
|
||||
@@ -31,6 +27,8 @@ import org.xml.sax.InputSource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.IDN;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
@@ -50,7 +48,6 @@ import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
@@ -92,7 +89,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
||||
protected boolean onSearchEnterPressed(TextView v, int actionId, KeyEvent event){
|
||||
if(event!=null && event.getAction()!=KeyEvent.ACTION_DOWN)
|
||||
return true;
|
||||
currentSearchQuery=searchEdit.getText().toString().toLowerCase();
|
||||
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim();
|
||||
updateFilteredList();
|
||||
searchEdit.removeCallbacks(searchDebouncer);
|
||||
Instance instance=instancesCache.get(normalizeInstanceDomain(currentSearchQuery));
|
||||
@@ -106,52 +103,16 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
||||
}
|
||||
|
||||
protected void onSearchChangedDebounced(){
|
||||
currentSearchQuery=searchEdit.getText().toString().toLowerCase();
|
||||
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim();
|
||||
updateFilteredList();
|
||||
loadInstanceInfo(currentSearchQuery, false);
|
||||
}
|
||||
|
||||
protected List<CatalogInstance> sortInstances(List<CatalogInstance> result){
|
||||
Map<String, List<CatalogInstance>> byLang=result.stream().collect(Collectors.groupingBy(ci->ci.language));
|
||||
for(List<CatalogInstance> group:byLang.values()){
|
||||
Collections.sort(group, (a, b)->{
|
||||
double aa=Math.abs(DUNBAR-Math.log(a.lastWeekUsers));
|
||||
double bb=Math.abs(DUNBAR-Math.log(b.lastWeekUsers));
|
||||
return Double.compare(aa, bb);
|
||||
});
|
||||
}
|
||||
// get the list of user-configured system languages
|
||||
List<String> userLangs;
|
||||
if(Build.VERSION.SDK_INT<24){
|
||||
userLangs=Collections.singletonList(getResources().getConfiguration().locale.getLanguage());
|
||||
}else{
|
||||
LocaleList ll=getResources().getConfiguration().getLocales();
|
||||
userLangs=new ArrayList<>(ll.size());
|
||||
for(int i=0;i<ll.size();i++){
|
||||
userLangs.add(ll.get(i).getLanguage());
|
||||
}
|
||||
}
|
||||
// add instances in preferred languages to the top of the list, in the order of preference
|
||||
Map<Boolean, List<CatalogInstance>> byLang=result.stream().sorted(Comparator.comparingInt((CatalogInstance ci)->ci.lastWeekUsers).reversed()).collect(Collectors.groupingBy(ci->ci.approvalRequired));
|
||||
ArrayList<CatalogInstance> sortedList=new ArrayList<>();
|
||||
for(String lang:userLangs){
|
||||
List<CatalogInstance> langInstances=byLang.remove(lang);
|
||||
if(langInstances!=null){
|
||||
sortedList.addAll(langInstances);
|
||||
}
|
||||
}
|
||||
// sort the remaining language groups by aggregate lastWeekUsers
|
||||
class InstanceGroup{
|
||||
public int activeUsers;
|
||||
public List<CatalogInstance> instances;
|
||||
}
|
||||
byLang.values().stream().map(il->{
|
||||
InstanceGroup group=new InstanceGroup();
|
||||
group.instances=il;
|
||||
for(CatalogInstance instance:il){
|
||||
group.activeUsers+=instance.lastWeekUsers;
|
||||
}
|
||||
return group;
|
||||
}).sorted(Comparator.comparingInt((InstanceGroup g)->g.activeUsers).reversed()).forEachOrdered(ig->sortedList.addAll(ig.instances));
|
||||
sortedList.addAll(byLang.getOrDefault(false, Collections.emptyList()));
|
||||
sortedList.addAll(byLang.getOrDefault(true, Collections.emptyList()));
|
||||
return sortedList;
|
||||
}
|
||||
|
||||
@@ -187,6 +148,8 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
||||
}
|
||||
|
||||
protected void loadInstanceInfo(String _domain, boolean isFromRedirect){
|
||||
if(TextUtils.isEmpty(_domain))
|
||||
return;
|
||||
String domain=normalizeInstanceDomain(_domain);
|
||||
Instance cachedInstance=instancesCache.get(domain);
|
||||
if(cachedInstance!=null){
|
||||
@@ -206,6 +169,20 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
||||
cancelLoadingInstanceInfo();
|
||||
}
|
||||
}
|
||||
try{
|
||||
new URI("https://"+domain+"/api/v1/instance"); // Validate the host by trying to parse the URI
|
||||
}catch(URISyntaxException x){
|
||||
showInstanceInfoLoadError(domain, x);
|
||||
if(fakeInstance!=null){
|
||||
fakeInstance.description=getString(R.string.error);
|
||||
if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){
|
||||
if(list.findViewHolderForAdapterPosition(1) instanceof BindableViewHolder<?> ivh){
|
||||
ivh.rebind();
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
loadingInstanceDomain=domain;
|
||||
loadingInstanceRequest=new GetInstance();
|
||||
loadingInstanceRequest.setCallback(new Callback<>(){
|
||||
@@ -222,7 +199,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
||||
}
|
||||
if(Objects.equals(domain, currentSearchQuery) || Objects.equals(currentSearchQuery, redirects.get(domain)) || Objects.equals(currentSearchQuery, redirectsInverse.get(domain))){
|
||||
boolean found=false;
|
||||
for(CatalogInstance ci : filteredData){
|
||||
for(CatalogInstance ci:filteredData){
|
||||
if(ci.domain.equals(domain) && ci!=fakeInstance){
|
||||
found=true;
|
||||
break;
|
||||
|
||||
@@ -1,39 +1,46 @@
|
||||
package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.os.Bundle;
|
||||
import android.os.LocaleList;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.Menu;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.HorizontalScrollView;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.api.requests.catalog.GetCatalogCategories;
|
||||
import org.joinmastodon.android.api.requests.catalog.GetCatalogInstances;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.catalog.CatalogCategory;
|
||||
import org.joinmastodon.android.model.catalog.CatalogInstance;
|
||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.tabs.TabLayout;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.FilterChipView;
|
||||
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -42,18 +49,33 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{
|
||||
private View headerView;
|
||||
public class InstanceCatalogSignupFragment extends InstanceCatalogFragment implements OnBackPressedListener{
|
||||
private MastodonAPIRequest<?> getCategoriesRequest;
|
||||
private TabLayout categoriesList;
|
||||
private String currentCategory="all";
|
||||
private List<CatalogCategory> categories=new ArrayList<>();
|
||||
private View topBar;
|
||||
|
||||
private List<String> languages=Collections.emptyList();
|
||||
private PopupMenu langFilterMenu, speedFilterMenu;
|
||||
private SignupSpeedFilter currentSignupSpeedFilter=SignupSpeedFilter.ANY;
|
||||
private String currentLanguage=null;
|
||||
private boolean searchQueryMode;
|
||||
private LinearLayout filtersWrap;
|
||||
private HorizontalScrollView filtersScroll;
|
||||
private ImageButton backBtn, clearSearchBtn;
|
||||
private View focusThing;
|
||||
|
||||
private FilterChipView categoryGeneral, categorySpecialInterests;
|
||||
private List<FilterChipView> regionalFilters;
|
||||
private CatalogInstance.Region chosenRegion;
|
||||
private CategoryChoice categoryChoice=CategoryChoice.GENERAL;
|
||||
|
||||
public InstanceCatalogSignupFragment(){
|
||||
super(R.layout.fragment_onboarding_common, 10);
|
||||
@@ -63,6 +85,12 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{
|
||||
public void onAttach(Context context){
|
||||
super.onAttach(context);
|
||||
setRefreshEnabled(false);
|
||||
setRetainInstance(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
loadData();
|
||||
}
|
||||
|
||||
@@ -75,6 +103,25 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
onDataLoaded(sortInstances(result), false);
|
||||
|
||||
if(langFilterMenu!=null){
|
||||
Menu menu=langFilterMenu.getMenu();
|
||||
menu.clear();
|
||||
menu.add(0, 0, 0, R.string.server_filter_any_language);
|
||||
languages=result.stream().map(i->i.language).distinct().filter(s->s.length()>0).sorted().collect(Collectors.toList());
|
||||
int i=1;
|
||||
for(String lang:languages){
|
||||
Locale locale=Locale.forLanguageTag(lang);
|
||||
String name=locale.getDisplayLanguage(locale);
|
||||
if(name.equals(lang))
|
||||
name=lang.toUpperCase();
|
||||
else
|
||||
name=name.substring(0, 1).toUpperCase()+name.substring(1);
|
||||
menu.add(0, i, 0, name);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
updateFilteredList();
|
||||
}
|
||||
|
||||
@@ -111,14 +158,14 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{
|
||||
}
|
||||
|
||||
private void updateCategories(){
|
||||
categoriesList.removeAllTabs();
|
||||
for(CatalogCategory cat:categories){
|
||||
int titleRes=getTitleForCategory(cat.category);
|
||||
TabLayout.Tab tab=categoriesList.newTab().setText(titleRes!=0 ? getString(titleRes) : cat.category).setCustomView(R.layout.item_instance_category);
|
||||
ImageView emoji=tab.getCustomView().findViewById(R.id.emoji);
|
||||
emoji.setImageResource(getEmojiForCategory(cat.category));
|
||||
categoriesList.addTab(tab);
|
||||
}
|
||||
// categoriesList.removeAllTabs();
|
||||
// for(CatalogCategory cat:categories){
|
||||
// int titleRes=getTitleForCategory(cat.category);
|
||||
// TabLayout.Tab tab=categoriesList.newTab().setText(titleRes!=0 ? getString(titleRes) : cat.category).setCustomView(R.layout.item_instance_category);
|
||||
// ImageView emoji=tab.getCustomView().findViewById(R.id.emoji);
|
||||
// emoji.setImageResource(getEmojiForCategory(cat.category));
|
||||
// categoriesList.addTab(tab);
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -130,27 +177,37 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter getAdapter(){
|
||||
headerView=getActivity().getLayoutInflater().inflate(R.layout.header_onboarding_instance_catalog, list, false);
|
||||
searchEdit=headerView.findViewById(R.id.search_edit);
|
||||
categoriesList=headerView.findViewById(R.id.categories_list);
|
||||
categoriesList.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){
|
||||
@Override
|
||||
public void onTabSelected(TabLayout.Tab tab){
|
||||
CatalogCategory category=categories.get(tab.getPosition());
|
||||
currentCategory=category.category;
|
||||
updateFilteredList();
|
||||
}
|
||||
View headerView=new View(getActivity());
|
||||
headerView.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1));
|
||||
|
||||
@Override
|
||||
public void onTabUnselected(TabLayout.Tab tab){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabReselected(TabLayout.Tab tab){
|
||||
mergeAdapter=new MergeRecyclerAdapter();
|
||||
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
||||
mergeAdapter.addAdapter(adapter=new InstancesAdapter());
|
||||
return mergeAdapter;
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
backBtn=view.findViewById(R.id.btn_back);
|
||||
backBtn.setOnClickListener(v->{
|
||||
if(searchQueryMode){
|
||||
setSearchQueryMode(false);
|
||||
}else{
|
||||
Nav.finish(this);
|
||||
}
|
||||
});
|
||||
clearSearchBtn=view.findViewById(R.id.clear);
|
||||
clearSearchBtn.setOnClickListener(v->searchEdit.setText(""));
|
||||
nextButton.setEnabled(true);
|
||||
list.setItemAnimator(new BetterItemAnimator());
|
||||
setStatusBarColor(0);
|
||||
topBar=view.findViewById(R.id.top_bar);
|
||||
|
||||
list.addOnScrollListener(new ElevationOnScrollListener(null, topBar, buttonBar));
|
||||
|
||||
searchEdit=view.findViewById(R.id.search_edit);
|
||||
searchEdit.setOnEditorActionListener(this::onSearchEnterPressed);
|
||||
searchEdit.addTextChangedListener(new TextWatcher(){
|
||||
@Override
|
||||
@@ -166,42 +223,162 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s){
|
||||
if((clearSearchBtn.getVisibility()==View.VISIBLE)!=(s.length()>0)){
|
||||
clearSearchBtn.setVisibility(s.length()>0 ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
});
|
||||
searchEdit.setOnFocusChangeListener((v, hasFocus)->{
|
||||
if(hasFocus && !searchQueryMode){
|
||||
setSearchQueryMode(true);
|
||||
}
|
||||
});
|
||||
|
||||
mergeAdapter=new MergeRecyclerAdapter();
|
||||
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
||||
mergeAdapter.addAdapter(adapter=new InstancesAdapter());
|
||||
return mergeAdapter;
|
||||
FilterChipView langFilter=new FilterChipView(getActivity());
|
||||
langFilter.setDrawableEnd(R.drawable.ic_baseline_arrow_drop_down_18);
|
||||
if(currentLanguage==null){
|
||||
langFilter.setText(R.string.server_filter_any_language);
|
||||
}else{
|
||||
Locale locale=Locale.forLanguageTag(currentLanguage);
|
||||
langFilter.setText(locale.getDisplayLanguage(locale));
|
||||
langFilter.setSelected(true);
|
||||
}
|
||||
langFilterMenu=new PopupMenu(getContext(), langFilter);
|
||||
langFilter.setOnTouchListener(langFilterMenu.getDragToOpenListener());
|
||||
langFilter.setOnClickListener(v->langFilterMenu.show());
|
||||
filtersWrap=view.findViewById(R.id.filters_container);
|
||||
filtersScroll=view.findViewById(R.id.filters_scroll);
|
||||
filtersWrap.addView(langFilter, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
FilterChipView speedFilter=new FilterChipView(getActivity());
|
||||
speedFilter.setDrawableEnd(R.drawable.ic_baseline_arrow_drop_down_18);
|
||||
speedFilterMenu=new PopupMenu(getContext(), speedFilter);
|
||||
speedFilterMenu.getMenu().add(0, 0, 0, R.string.server_filter_any_signup_speed);
|
||||
speedFilterMenu.getMenu().add(0, 1, 0, R.string.server_filter_instant_signup);
|
||||
speedFilterMenu.getMenu().add(0, 2, 0, R.string.server_filter_manual_review);
|
||||
speedFilter.setOnTouchListener(speedFilterMenu.getDragToOpenListener());
|
||||
speedFilter.setOnClickListener(v->speedFilterMenu.show());
|
||||
speedFilter.setText(switch(currentSignupSpeedFilter){
|
||||
case ANY -> R.string.server_filter_any_signup_speed;
|
||||
case INSTANT -> R.string.server_filter_instant_signup;
|
||||
case REVIEWED -> R.string.server_filter_manual_review;
|
||||
});
|
||||
speedFilter.setSelected(currentSignupSpeedFilter!=SignupSpeedFilter.ANY);
|
||||
filtersWrap.addView(speedFilter, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
speedFilterMenu.setOnMenuItemClickListener(item->{
|
||||
speedFilter.setText(item.getTitle());
|
||||
speedFilter.setSelected(item.getItemId()>0);
|
||||
currentSignupSpeedFilter=SignupSpeedFilter.values()[item.getItemId()];
|
||||
updateFilteredList();
|
||||
return true;
|
||||
});
|
||||
langFilterMenu.setOnMenuItemClickListener(item->{
|
||||
langFilter.setText(item.getTitle());
|
||||
langFilter.setSelected(item.getItemId()>0);
|
||||
currentLanguage=item.getItemId()==0 ? null : languages.get(item.getItemId()-1);
|
||||
updateFilteredList();
|
||||
return true;
|
||||
});
|
||||
|
||||
View divider=new View(getActivity());
|
||||
divider.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Outline));
|
||||
filtersWrap.addView(divider, new LinearLayout.LayoutParams(V.dp(.5f), ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
categoryGeneral=new FilterChipView(getActivity());
|
||||
categoryGeneral.setText(R.string.category_general);
|
||||
categoryGeneral.setTag(CategoryChoice.GENERAL);
|
||||
categoryGeneral.setOnClickListener(this::onCategoryFilterClick);
|
||||
categoryGeneral.setSelected(categoryChoice==CategoryChoice.GENERAL);
|
||||
filtersWrap.addView(categoryGeneral, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
categorySpecialInterests=new FilterChipView(getActivity());
|
||||
categorySpecialInterests.setText(R.string.category_special_interests);
|
||||
categorySpecialInterests.setTag(CategoryChoice.SPECIAL);
|
||||
categorySpecialInterests.setOnClickListener(this::onCategoryFilterClick);
|
||||
categorySpecialInterests.setSelected(categoryChoice==CategoryChoice.SPECIAL);
|
||||
filtersWrap.addView(categorySpecialInterests, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
regionalFilters=Arrays.stream(CatalogInstance.Region.values()).map(r->{
|
||||
FilterChipView fv=new FilterChipView(getActivity());
|
||||
fv.setTag(r);
|
||||
fv.setText(switch(r){
|
||||
case EUROPE -> R.string.server_filter_region_europe;
|
||||
case NORTH_AMERICA -> R.string.server_filter_region_north_america;
|
||||
case SOUTH_AMERICA -> R.string.server_filter_region_south_america;
|
||||
case AFRICA -> R.string.server_filter_region_africa;
|
||||
case ASIA -> R.string.server_filter_region_asia;
|
||||
case OCEANIA -> R.string.server_filter_region_oceania;
|
||||
});
|
||||
fv.setSelected(r==chosenRegion);
|
||||
fv.setOnClickListener(this::onRegionFilterClick);
|
||||
filtersWrap.addView(fv, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
return fv;
|
||||
}).collect(Collectors.toList());
|
||||
focusThing=view.findViewById(R.id.focus_thing);
|
||||
focusThing.requestFocus();
|
||||
|
||||
view.findViewById(R.id.btn_random_instance).setOnClickListener(this::onPickRandomInstanceClick);
|
||||
nextButton.setEnabled(chosenInstance!=null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
|
||||
list.setItemAnimator(new BetterItemAnimator());
|
||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 16, 16, DividerItemDecoration.NOT_FIRST));
|
||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
||||
private void onRegionFilterClick(View v){
|
||||
CatalogInstance.Region r=(CatalogInstance.Region) v.getTag();
|
||||
if(chosenRegion==r){
|
||||
chosenRegion=null;
|
||||
v.setSelected(false);
|
||||
}else{
|
||||
if(chosenRegion!=null)
|
||||
filtersWrap.findViewWithTag(chosenRegion).setSelected(false);
|
||||
chosenRegion=r;
|
||||
v.setSelected(true);
|
||||
}
|
||||
updateFilteredList();
|
||||
}
|
||||
|
||||
private void onCategoryFilterClick(View v){
|
||||
CategoryChoice c=(CategoryChoice) v.getTag();
|
||||
if(categoryChoice==c){
|
||||
categoryChoice=null;
|
||||
v.setSelected(false);
|
||||
}else{
|
||||
if(categoryChoice!=null)
|
||||
filtersWrap.findViewWithTag(categoryChoice).setSelected(false);
|
||||
categoryChoice=c;
|
||||
v.setSelected(true);
|
||||
}
|
||||
updateFilteredList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void proceedWithAuthOrSignup(Instance instance){
|
||||
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0);
|
||||
if(isSignup){
|
||||
if(!instance.registrations){
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(R.string.instance_signup_closed)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
Bundle args=new Bundle();
|
||||
args.putParcelable("instance", Parcels.wrap(instance));
|
||||
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
||||
}else{
|
||||
if(!instance.registrations){
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(R.string.instance_signup_closed)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
Bundle args=new Bundle();
|
||||
args.putParcelable("instance", Parcels.wrap(instance));
|
||||
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
||||
}
|
||||
|
||||
private void onPickRandomInstanceClick(View v){
|
||||
String lang=Locale.getDefault().getLanguage();
|
||||
List<CatalogInstance> instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general"))) && (lang.equals(ci.language) || (ci.languages!=null && ci.languages.contains(lang)))).collect(Collectors.toList());
|
||||
if(instances.isEmpty()){
|
||||
instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general")))).collect(Collectors.toList());
|
||||
}
|
||||
if(instances.isEmpty()){
|
||||
instances=data.stream().filter(ci->("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general")))).collect(Collectors.toList());
|
||||
}
|
||||
if(instances.isEmpty()){
|
||||
return;
|
||||
}
|
||||
chosenInstance=instances.get(new Random().nextInt(instances.size()));
|
||||
onNextClick(v);
|
||||
}
|
||||
|
||||
// private String getEmojiForCategory(String category){
|
||||
@@ -265,11 +442,29 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{
|
||||
protected void updateFilteredList(){
|
||||
ArrayList<CatalogInstance> prevData=new ArrayList<>(filteredData);
|
||||
filteredData.clear();
|
||||
for(CatalogInstance instance:data){
|
||||
if(currentCategory.equals("all") || instance.categories.contains(currentCategory)){
|
||||
if(TextUtils.isEmpty(currentSearchQuery) || instance.domain.contains(currentSearchQuery)){
|
||||
if(instance.domain.equals(currentSearchQuery) || !isSignup || !instance.approvalRequired)
|
||||
if(searchQueryMode){
|
||||
if(!TextUtils.isEmpty(currentSearchQuery)){
|
||||
for(CatalogInstance instance:data){
|
||||
if(instance.domain.contains(currentSearchQuery)){
|
||||
filteredData.add(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
for(CatalogInstance instance:data){
|
||||
if(categoryChoice==null || categoryChoice.matches(instance.category)){
|
||||
if(chosenRegion==null || instance.region==chosenRegion){
|
||||
boolean signupSpeedMatches=switch(currentSignupSpeedFilter){
|
||||
case ANY -> true;
|
||||
case INSTANT -> !instance.approvalRequired;
|
||||
case REVIEWED -> instance.approvalRequired;
|
||||
};
|
||||
if(signupSpeedMatches){
|
||||
if(currentLanguage==null || instance.languages.contains(currentLanguage)){
|
||||
filteredData.add(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -296,6 +491,53 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{
|
||||
}).dispatchUpdatesTo(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyWindowInsets(WindowInsets insets){
|
||||
topBar.setPadding(0, insets.getSystemWindowInsetTop(), 0, 0);
|
||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed(){
|
||||
if(searchQueryMode){
|
||||
setSearchQueryMode(false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void setSearchQueryMode(boolean enabled){
|
||||
searchQueryMode=enabled;
|
||||
RelativeLayout.LayoutParams lp=(RelativeLayout.LayoutParams) searchEdit.getLayoutParams();
|
||||
if(searchQueryMode){
|
||||
filtersScroll.setVisibility(View.GONE);
|
||||
lp.removeRule(RelativeLayout.END_OF);
|
||||
backBtn.setScaleX(0.83333333f);
|
||||
backBtn.setScaleY(0.83333333f);
|
||||
backBtn.setTranslationX(V.dp(8));
|
||||
searchEdit.setCompoundDrawableTintList(ColorStateList.valueOf(0));
|
||||
}else{
|
||||
filtersScroll.setVisibility(View.VISIBLE);
|
||||
focusThing.requestFocus();
|
||||
searchEdit.setText("");
|
||||
lp.addRule(RelativeLayout.END_OF, R.id.btn_back);
|
||||
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(searchEdit.getWindowToken(), 0);
|
||||
backBtn.setScaleX(1);
|
||||
backBtn.setScaleY(1);
|
||||
backBtn.setTranslationX(0);
|
||||
searchEdit.setCompoundDrawableTintList(ColorStateList.valueOf(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant)));
|
||||
}
|
||||
updateFilteredList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShown(){
|
||||
super.onShown();
|
||||
if(!searchQueryMode){
|
||||
// Prevent search view automatically getting focused when the user returns to this fragment
|
||||
focusThing.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
private class InstancesAdapter extends UsableRecyclerView.Adapter<InstanceCatalogSignupFragment.InstanceViewHolder>{
|
||||
public InstancesAdapter(){
|
||||
@@ -325,30 +567,36 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{
|
||||
}
|
||||
}
|
||||
|
||||
private class InstanceViewHolder extends BindableViewHolder<CatalogInstance> implements UsableRecyclerView.Clickable{
|
||||
private final TextView title, description, userCount, lang;
|
||||
private class InstanceViewHolder extends BindableViewHolder<CatalogInstance> implements UsableRecyclerView.DisableableClickable{
|
||||
private final TextView title, description;
|
||||
private final RadioButton radioButton;
|
||||
private boolean enabled;
|
||||
|
||||
public InstanceViewHolder(){
|
||||
super(getActivity(), R.layout.item_instance_catalog, list);
|
||||
title=findViewById(R.id.title);
|
||||
description=findViewById(R.id.description);
|
||||
userCount=findViewById(R.id.user_count);
|
||||
lang=findViewById(R.id.lang);
|
||||
radioButton=findViewById(R.id.radiobtn);
|
||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N){
|
||||
UiUtils.fixCompoundDrawableTintOnAndroid6(userCount);
|
||||
UiUtils.fixCompoundDrawableTintOnAndroid6(lang);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(CatalogInstance item){
|
||||
title.setText(item.normalizedDomain);
|
||||
description.setText(item.description);
|
||||
userCount.setText(UiUtils.abbreviateNumber(item.totalUsers));
|
||||
lang.setText(item.language.toUpperCase());
|
||||
radioButton.setChecked(chosenInstance==item);
|
||||
Instance realInstance=instancesCache.get(item.normalizedDomain);
|
||||
float alpha;
|
||||
if(realInstance!=null && !realInstance.registrations){
|
||||
alpha=0.38f;
|
||||
description.setText(R.string.not_accepting_new_members);
|
||||
enabled=false;
|
||||
}else{
|
||||
alpha=1f;
|
||||
description.setText(item.description);
|
||||
enabled=true;
|
||||
}
|
||||
title.setAlpha(alpha);
|
||||
description.setAlpha(alpha);
|
||||
radioButton.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -358,17 +606,49 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{
|
||||
if(chosenInstance!=null){
|
||||
int idx=filteredData.indexOf(chosenInstance);
|
||||
if(idx!=-1){
|
||||
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(mergeAdapter.getPositionForAdapter(adapter)+idx);
|
||||
if(holder instanceof InstanceCatalogSignupFragment.InstanceViewHolder ivh){
|
||||
ivh.radioButton.setChecked(false);
|
||||
boolean found=false;
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||
if(holder.getAbsoluteAdapterPosition()==mergeAdapter.getPositionForAdapter(adapter)+idx && holder instanceof InstanceViewHolder ivh){
|
||||
ivh.radioButton.setChecked(false);
|
||||
found=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!found)
|
||||
adapter.notifyItemChanged(idx);
|
||||
}
|
||||
}
|
||||
if(!nextButton.isEnabled()){
|
||||
nextButton.setEnabled(true);
|
||||
}
|
||||
radioButton.setChecked(true);
|
||||
if(chosenInstance==null)
|
||||
nextButton.setEnabled(true);
|
||||
chosenInstance=item;
|
||||
loadInstanceInfo(chosenInstance.domain, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(){
|
||||
return enabled;
|
||||
}
|
||||
}
|
||||
|
||||
private enum SignupSpeedFilter{
|
||||
ANY,
|
||||
INSTANT,
|
||||
REVIEWED
|
||||
}
|
||||
|
||||
private enum CategoryChoice{
|
||||
GENERAL,
|
||||
SPECIAL;
|
||||
|
||||
public boolean matches(String category){
|
||||
boolean isGeneral=(category==null || "general".equals(category));
|
||||
return (this==GENERAL)==isGeneral;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -240,13 +240,17 @@ public class InstanceChooserLoginFragment extends InstanceCatalogFragment{
|
||||
if(chosenInstance!=null){
|
||||
int idx=filteredData.indexOf(chosenInstance);
|
||||
if(idx!=-1){
|
||||
boolean found=false;
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||
if(holder.getAbsoluteAdapterPosition()==mergeAdapter.getPositionForAdapter(adapter)+idx && holder instanceof InstanceViewHolder ivh){
|
||||
ivh.radioButton.setChecked(false);
|
||||
found=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!found)
|
||||
adapter.notifyItemChanged(idx);
|
||||
}
|
||||
}
|
||||
radioButton.setChecked(true);
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.text.style.TypefaceSpan;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -16,25 +23,30 @@ import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.fragments.AppKitFragment;
|
||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class InstanceRulesFragment extends AppKitFragment{
|
||||
public class InstanceRulesFragment extends ToolbarFragment{
|
||||
private UsableRecyclerView list;
|
||||
private MergeRecyclerAdapter adapter;
|
||||
private Button btn;
|
||||
private View buttonBar;
|
||||
private Instance instance;
|
||||
private ElevationOnScrollListener onScrollListener;
|
||||
|
||||
private static final int RULES_REQUEST=376;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -47,30 +59,29 @@ public class InstanceRulesFragment extends AppKitFragment{
|
||||
super.onAttach(activity);
|
||||
setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground));
|
||||
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
|
||||
setTitle(R.string.instance_rules_title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||
View view=inflater.inflate(R.layout.fragment_onboarding_rules, container, false);
|
||||
|
||||
list=view.findViewById(R.id.list);
|
||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
View headerView=inflater.inflate(R.layout.item_list_header, list, false);
|
||||
TextView title=headerView.findViewById(R.id.title);
|
||||
TextView subtitle=headerView.findViewById(R.id.subtitle);
|
||||
headerView.findViewById(R.id.step_counter).setVisibility(View.GONE);
|
||||
title.setText(R.string.instance_rules_title);
|
||||
subtitle.setText(getString(R.string.instance_rules_subtitle, instance.uri));
|
||||
View headerView=inflater.inflate(R.layout.item_list_header_simple, list, false);
|
||||
TextView text=headerView.findViewById(R.id.text);
|
||||
text.setText(Html.fromHtml(getString(R.string.instance_rules_subtitle, "<b>"+Html.escapeHtml(instance.uri)+"</b>")));
|
||||
|
||||
adapter=new MergeRecyclerAdapter();
|
||||
adapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
||||
adapter.addAdapter(new ItemsAdapter());
|
||||
list.setAdapter(adapter);
|
||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 16, 16, DividerItemDecoration.NOT_FIRST));
|
||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3SurfaceVariant, 1, 56, 0, DividerItemDecoration.NOT_FIRST));
|
||||
|
||||
btn=view.findViewById(R.id.btn_next);
|
||||
btn.setOnClickListener(v->onButtonClick());
|
||||
buttonBar=view.findViewById(R.id.button_bar);
|
||||
|
||||
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
|
||||
|
||||
return view;
|
||||
@@ -79,13 +90,33 @@ public class InstanceRulesFragment extends AppKitFragment{
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
list.addOnScrollListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUpdateToolbar(){
|
||||
super.onUpdateToolbar();
|
||||
getToolbar().setBackgroundResource(R.drawable.bg_onboarding_panel);
|
||||
getToolbar().setElevation(0);
|
||||
if(onScrollListener!=null){
|
||||
onScrollListener.setViews(buttonBar, getToolbar());
|
||||
}
|
||||
}
|
||||
|
||||
protected void onButtonClick(){
|
||||
Bundle args=new Bundle();
|
||||
args.putParcelable("instance", Parcels.wrap(instance));
|
||||
Nav.go(getActivity(), GoogleMadeMeAddThisFragment.class, args);
|
||||
Nav.goForResult(getActivity(), GoogleMadeMeAddThisFragment.class, args, RULES_REQUEST, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFragmentResult(int reqCode, boolean success, Bundle result){
|
||||
super.onFragmentResult(reqCode, success, result);
|
||||
if(reqCode==RULES_REQUEST && !success){
|
||||
Nav.finish(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -119,23 +150,22 @@ public class InstanceRulesFragment extends AppKitFragment{
|
||||
}
|
||||
|
||||
private class ItemViewHolder extends BindableViewHolder<Instance.Rule>{
|
||||
private final TextView title, subtitle;
|
||||
private final ImageView checkbox;
|
||||
private final TextView text, number;
|
||||
|
||||
public ItemViewHolder(){
|
||||
super(getActivity(), R.layout.item_report_choice, list);
|
||||
title=findViewById(R.id.title);
|
||||
subtitle=findViewById(R.id.subtitle);
|
||||
checkbox=findViewById(R.id.checkbox);
|
||||
subtitle.setVisibility(View.GONE);
|
||||
super(getActivity(), R.layout.item_server_rule, list);
|
||||
text=findViewById(R.id.text);
|
||||
number=findViewById(R.id.number);
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
@Override
|
||||
public void onBind(Instance.Rule item){
|
||||
if(item.parsedText==null){
|
||||
item.parsedText=HtmlParser.parseLinks(item.text);
|
||||
}
|
||||
title.setText(item.parsedText);
|
||||
text.setText(item.parsedText);
|
||||
number.setText(String.format("%d", getAbsoluteAdapterPosition()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,344 @@
|
||||
package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
|
||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
||||
import org.joinmastodon.android.fragments.HomeFragment;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
import org.joinmastodon.android.model.FollowSuggestion;
|
||||
import org.joinmastodon.android.model.ParsedAccount;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class OnboardingFollowSuggestionsFragment extends BaseRecyclerFragment<ParsedAccount>{
|
||||
private String accountID;
|
||||
private Map<String, Relationship> relationships=Collections.emptyMap();
|
||||
private GetAccountRelationships relationshipsRequest;
|
||||
private View buttonBar;
|
||||
private ElevationOnScrollListener onScrollListener;
|
||||
private int numRunningFollowRequests=0;
|
||||
|
||||
public OnboardingFollowSuggestionsFragment(){
|
||||
super(R.layout.fragment_onboarding_follow_suggestions, 40);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
setRetainInstance(true);
|
||||
setTitle(R.string.popular_on_mastodon);
|
||||
accountID=getArguments().getString("account");
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
buttonBar=view.findViewById(R.id.button_bar);
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
list.addOnScrollListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
|
||||
|
||||
view.findViewById(R.id.btn_next).setOnClickListener(UiUtils.rateLimitedClickListener(this::onFollowAllClick));
|
||||
view.findViewById(R.id.btn_skip).setOnClickListener(UiUtils.rateLimitedClickListener(v->proceed()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUpdateToolbar(){
|
||||
super.onUpdateToolbar();
|
||||
getToolbar().setBackgroundResource(R.drawable.bg_onboarding_panel);
|
||||
getToolbar().setElevation(0);
|
||||
if(onScrollListener!=null){
|
||||
onScrollListener.setViews(buttonBar, getToolbar());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
new GetFollowSuggestions(40)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<FollowSuggestion> result){
|
||||
onDataLoaded(result.stream().map(fs->new ParsedAccount(fs.account, accountID)).collect(Collectors.toList()), false);
|
||||
loadRelationships();
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
private void loadRelationships(){
|
||||
relationships=Collections.emptyMap();
|
||||
relationshipsRequest=new GetAccountRelationships(data.stream().map(fs->fs.account.id).collect(Collectors.toList()));
|
||||
relationshipsRequest.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<Relationship> result){
|
||||
relationshipsRequest=null;
|
||||
relationships=result.stream().collect(Collectors.toMap(rel->rel.id, Function.identity()));
|
||||
if(list==null)
|
||||
return;
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||
if(holder instanceof SuggestionViewHolder svh)
|
||||
svh.rebind();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
relationshipsRequest=null;
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyWindowInsets(WindowInsets insets){
|
||||
if(Build.VERSION.SDK_INT>=27){
|
||||
int inset=insets.getSystemWindowInsetBottom();
|
||||
buttonBar.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0);
|
||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), 0));
|
||||
}else{
|
||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter getAdapter(){
|
||||
return new SuggestionsAdapter();
|
||||
}
|
||||
|
||||
private void onFollowAllClick(View v){
|
||||
if(!loaded || relationships.isEmpty())
|
||||
return;
|
||||
if(data.isEmpty()){
|
||||
proceed();
|
||||
return;
|
||||
}
|
||||
ArrayList<String> accountIdsToFollow=new ArrayList<>();
|
||||
for(ParsedAccount acc:data){
|
||||
Relationship rel=relationships.get(acc.account.id);
|
||||
if(rel==null)
|
||||
continue;
|
||||
if(rel.canFollow())
|
||||
accountIdsToFollow.add(acc.account.id);
|
||||
}
|
||||
|
||||
final ProgressDialog progress=new ProgressDialog(getActivity());
|
||||
progress.setIndeterminate(false);
|
||||
progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
progress.setMax(accountIdsToFollow.size());
|
||||
progress.setCancelable(false);
|
||||
progress.setMessage(getString(R.string.sending_follows));
|
||||
progress.show();
|
||||
|
||||
for(int i=0;i<Math.min(accountIdsToFollow.size(), 5);i++){ // Send up to 5 requests in parallel
|
||||
followNextAccount(accountIdsToFollow, progress);
|
||||
}
|
||||
}
|
||||
|
||||
private void followNextAccount(ArrayList<String> accountIdsToFollow, ProgressDialog progress){
|
||||
if(accountIdsToFollow.isEmpty()){
|
||||
if(numRunningFollowRequests==0){
|
||||
progress.dismiss();
|
||||
proceed();
|
||||
}
|
||||
return;
|
||||
}
|
||||
numRunningFollowRequests++;
|
||||
String id=accountIdsToFollow.remove(0);
|
||||
new SetAccountFollowed(id, true, true)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
relationships.put(id, result);
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
if(list.getChildViewHolder(list.getChildAt(i)) instanceof SuggestionViewHolder svh && svh.getItem().account.id.equals(id)){
|
||||
svh.rebind();
|
||||
break;
|
||||
}
|
||||
}
|
||||
numRunningFollowRequests--;
|
||||
progress.setProgress(progress.getMax()-accountIdsToFollow.size()-numRunningFollowRequests);
|
||||
followNextAccount(accountIdsToFollow, progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
numRunningFollowRequests--;
|
||||
progress.setProgress(progress.getMax()-accountIdsToFollow.size()-numRunningFollowRequests);
|
||||
followNextAccount(accountIdsToFollow, progress);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
private void proceed(){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.go(getActivity(), OnboardingProfileSetupFragment.class, args);
|
||||
}
|
||||
|
||||
private class SuggestionsAdapter extends UsableRecyclerView.Adapter<SuggestionViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
|
||||
public SuggestionsAdapter(){
|
||||
super(imgLoader);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public SuggestionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new SuggestionViewHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return data.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(SuggestionViewHolder holder, int position){
|
||||
holder.bind(data.get(position));
|
||||
super.onBindViewHolder(holder, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getImageCountForItem(int position){
|
||||
return data.get(position).emojiHelper.getImageCount()+1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||
ParsedAccount account=data.get(position);
|
||||
if(image==0)
|
||||
return account.avatarRequest;
|
||||
return account.emojiHelper.getImageRequest(image-1);
|
||||
}
|
||||
}
|
||||
|
||||
private class SuggestionViewHolder extends BindableViewHolder<ParsedAccount> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable{
|
||||
private final TextView name, username, bio;
|
||||
private final ImageView avatar;
|
||||
private final ProgressBarButton actionButton;
|
||||
private final ProgressBar actionProgress;
|
||||
private final View actionWrap;
|
||||
|
||||
private Relationship relationship;
|
||||
|
||||
public SuggestionViewHolder(){
|
||||
super(getActivity(), R.layout.item_user_row_m3, list);
|
||||
name=findViewById(R.id.name);
|
||||
username=findViewById(R.id.username);
|
||||
bio=findViewById(R.id.bio);
|
||||
avatar=findViewById(R.id.avatar);
|
||||
actionButton=findViewById(R.id.action_btn);
|
||||
actionProgress=findViewById(R.id.action_progress);
|
||||
actionWrap=findViewById(R.id.action_btn_wrap);
|
||||
|
||||
avatar.setOutlineProvider(OutlineProviders.roundedRect(10));
|
||||
avatar.setClipToOutline(true);
|
||||
actionButton.setOnClickListener(UiUtils.rateLimitedClickListener(this::onActionButtonClick));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(ParsedAccount item){
|
||||
name.setText(item.parsedName);
|
||||
username.setText(item.account.getDisplayUsername());
|
||||
if(TextUtils.isEmpty(item.parsedBio)){
|
||||
bio.setVisibility(View.GONE);
|
||||
}else{
|
||||
bio.setVisibility(View.VISIBLE);
|
||||
bio.setText(item.parsedBio);
|
||||
}
|
||||
|
||||
relationship=relationships.get(item.account.id);
|
||||
if(relationship==null){
|
||||
actionWrap.setVisibility(View.GONE);
|
||||
}else{
|
||||
actionWrap.setVisibility(View.VISIBLE);
|
||||
UiUtils.setRelationshipToActionButtonM3(relationship, actionButton);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImage(int index, Drawable image){
|
||||
if(index==0){
|
||||
avatar.setImageDrawable(image);
|
||||
}else{
|
||||
item.emojiHelper.setImageDrawable(index-1, image);
|
||||
name.invalidate();
|
||||
bio.invalidate();
|
||||
}
|
||||
if(image instanceof Animatable a && !a.isRunning())
|
||||
a.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearImage(int index){
|
||||
setImage(index, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("profileAccount", Parcels.wrap(item.account));
|
||||
Nav.go(getActivity(), ProfileFragment.class, args);
|
||||
}
|
||||
|
||||
private void onActionButtonClick(View v){
|
||||
itemView.setHasTransientState(true);
|
||||
UiUtils.performAccountAction(getActivity(), item.account, accountID, relationship, actionButton, this::setActionProgressVisible, rel->{
|
||||
itemView.setHasTransientState(false);
|
||||
relationships.put(item.account.id, rel);
|
||||
rebind();
|
||||
});
|
||||
}
|
||||
|
||||
private void setActionProgressVisible(boolean visible){
|
||||
actionButton.setTextVisible(!visible);
|
||||
actionProgress.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
actionButton.setClickable(!visible);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.HomeFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.AccountField;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.ReorderableLinearLayout;
|
||||
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
|
||||
public class OnboardingProfileSetupFragment extends ToolbarFragment implements ReorderableLinearLayout.OnDragListener{
|
||||
private Button btn;
|
||||
private View buttonBar;
|
||||
private String accountID;
|
||||
private ElevationOnScrollListener onScrollListener;
|
||||
private ScrollView scroller;
|
||||
private EditText nameEdit, bioEdit;
|
||||
private ImageView avaImage, coverImage;
|
||||
private Button addRow;
|
||||
private ReorderableLinearLayout profileFieldsLayout;
|
||||
private Uri avatarUri, coverUri;
|
||||
|
||||
private static final int AVATAR_RESULT=348;
|
||||
private static final int COVER_RESULT=183;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
setRetainInstance(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground));
|
||||
accountID=getArguments().getString("account");
|
||||
setTitle(R.string.profile_setup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||
View view=inflater.inflate(R.layout.fragment_onboarding_profile_setup, container, false);
|
||||
|
||||
scroller=view.findViewById(R.id.scroller);
|
||||
nameEdit=view.findViewById(R.id.display_name);
|
||||
bioEdit=view.findViewById(R.id.bio);
|
||||
avaImage=view.findViewById(R.id.avatar);
|
||||
coverImage=view.findViewById(R.id.header);
|
||||
addRow=view.findViewById(R.id.add_row);
|
||||
profileFieldsLayout=view.findViewById(R.id.profile_fields);
|
||||
|
||||
btn=view.findViewById(R.id.btn_next);
|
||||
btn.setOnClickListener(v->onButtonClick());
|
||||
buttonBar=view.findViewById(R.id.button_bar);
|
||||
|
||||
avaImage.setOutlineProvider(OutlineProviders.roundedRect(24));
|
||||
avaImage.setClipToOutline(true);
|
||||
|
||||
Account account=AccountSessionManager.getInstance().getAccount(accountID).self;
|
||||
if(savedInstanceState==null){
|
||||
nameEdit.setText(account.displayName);
|
||||
makeFieldsRow();
|
||||
}else{
|
||||
ArrayList<String> fieldTitles=savedInstanceState.getStringArrayList("fieldTitles");
|
||||
ArrayList<String> fieldValues=savedInstanceState.getStringArrayList("fieldValues");
|
||||
for(int i=0;i<fieldTitles.size();i++){
|
||||
View row=makeFieldsRow();
|
||||
EditText title=row.findViewById(R.id.title);
|
||||
EditText content=row.findViewById(R.id.content);
|
||||
title.setText(fieldTitles.get(i));
|
||||
content.setText(fieldValues.get(i));
|
||||
}
|
||||
if(fieldTitles.size()==4)
|
||||
addRow.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
addRow.setOnClickListener(v->{
|
||||
makeFieldsRow();
|
||||
if(profileFieldsLayout.getChildCount()==4){
|
||||
addRow.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
profileFieldsLayout.setDragListener(this);
|
||||
avaImage.setOnClickListener(v->startActivityForResult(UiUtils.getMediaPickerIntent(new String[]{"image/*"}, 1), AVATAR_RESULT));
|
||||
coverImage.setOnClickListener(v->startActivityForResult(UiUtils.getMediaPickerIntent(new String[]{"image/*"}, 1), COVER_RESULT));
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
scroller.setOnScrollChangeListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUpdateToolbar(){
|
||||
super.onUpdateToolbar();
|
||||
getToolbar().setBackgroundResource(R.drawable.bg_onboarding_panel);
|
||||
getToolbar().setElevation(0);
|
||||
if(onScrollListener!=null){
|
||||
onScrollListener.setViews(buttonBar, getToolbar());
|
||||
}
|
||||
}
|
||||
|
||||
protected void onButtonClick(){
|
||||
ArrayList<AccountField> fields=new ArrayList<>();
|
||||
for(int i=0;i<profileFieldsLayout.getChildCount();i++){
|
||||
View row=profileFieldsLayout.getChildAt(i);
|
||||
EditText title=row.findViewById(R.id.title);
|
||||
EditText content=row.findViewById(R.id.content);
|
||||
AccountField fld=new AccountField();
|
||||
fld.name=title.getText().toString();
|
||||
fld.value=content.getText().toString();
|
||||
fields.add(fld);
|
||||
}
|
||||
new UpdateAccountCredentials(nameEdit.getText().toString(), bioEdit.getText().toString(), avatarUri, coverUri, fields)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Account result){
|
||||
AccountSessionManager.getInstance().updateAccountInfo(accountID, result);
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.goClearingStack(getActivity(), HomeFragment.class, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(getActivity());
|
||||
}
|
||||
})
|
||||
.wrapProgress(getActivity(), R.string.saving, true)
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyWindowInsets(WindowInsets insets){
|
||||
if(Build.VERSION.SDK_INT>=27){
|
||||
int inset=insets.getSystemWindowInsetBottom();
|
||||
buttonBar.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0);
|
||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), 0));
|
||||
}else{
|
||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
||||
}
|
||||
}
|
||||
|
||||
private View makeFieldsRow(){
|
||||
View view=LayoutInflater.from(getActivity()).inflate(R.layout.onboarding_profile_field, profileFieldsLayout, false);
|
||||
profileFieldsLayout.addView(view);
|
||||
view.findViewById(R.id.dragger_thingy).setOnLongClickListener(v->{
|
||||
profileFieldsLayout.startDragging(view);
|
||||
return true;
|
||||
});
|
||||
view.findViewById(R.id.delete).setOnClickListener(v->{
|
||||
profileFieldsLayout.removeView(view);
|
||||
if(addRow.getVisibility()==View.GONE)
|
||||
addRow.setVisibility(View.VISIBLE);
|
||||
});
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwapItems(int oldIndex, int newIndex){}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState){
|
||||
super.onSaveInstanceState(outState);
|
||||
ArrayList<String> fieldTitles=new ArrayList<>(), fieldValues=new ArrayList<>();
|
||||
for(int i=0;i<profileFieldsLayout.getChildCount();i++){
|
||||
View row=profileFieldsLayout.getChildAt(i);
|
||||
EditText title=row.findViewById(R.id.title);
|
||||
EditText content=row.findViewById(R.id.content);
|
||||
fieldTitles.add(title.getText().toString());
|
||||
fieldValues.add(content.getText().toString());
|
||||
}
|
||||
outState.putStringArrayList("fieldTitles", fieldTitles);
|
||||
outState.putStringArrayList("fieldValues", fieldValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}else{
|
||||
img=coverImage;
|
||||
coverUri=uri;
|
||||
size=V.dp(1000);
|
||||
}
|
||||
img.setForeground(null);
|
||||
ViewImageLoader.load(img, null, new UrlImageLoaderRequest(uri, size, size));
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,18 @@
|
||||
package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.Html;
|
||||
import android.text.SpannableString;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.text.style.TypefaceSpan;
|
||||
import android.text.style.URLSpan;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -16,32 +20,35 @@ import android.view.ViewTreeObserver;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.api.MastodonDetailedErrorResponse;
|
||||
import org.joinmastodon.android.api.requests.accounts.RegisterAccount;
|
||||
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
|
||||
import org.joinmastodon.android.api.requests.oauth.GetOauthToken;
|
||||
import org.joinmastodon.android.api.session.AccountActivationInfo;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Application;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.Token;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.text.LinkSpan;
|
||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.FloatingHintEditTextLayout;
|
||||
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.nodes.Node;
|
||||
import org.jsoup.nodes.TextNode;
|
||||
import org.jsoup.select.NodeVisitor;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -49,31 +56,28 @@ import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.APIRequest;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.AppKitFragment;
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
|
||||
public class SignupFragment extends AppKitFragment{
|
||||
private static final int AVATAR_RESULT=198;
|
||||
public class SignupFragment extends ToolbarFragment{
|
||||
private static final String TAG="SignupFragment";
|
||||
|
||||
private Instance instance;
|
||||
|
||||
private EditText displayName, username, email, password, reason;
|
||||
private EditText displayName, username, email, password, passwordConfirm, reason;
|
||||
private FloatingHintEditTextLayout displayNameWrap, usernameWrap, emailWrap, passwordWrap, passwordConfirmWrap, reasonWrap;
|
||||
private TextView reasonExplain;
|
||||
private Button btn;
|
||||
private View buttonBar;
|
||||
private TextWatcher buttonStateUpdater=new SimpleTextWatcher(e->updateButtonState());
|
||||
private ImageView avatar;
|
||||
private APIRequest currentBackgroundRequest;
|
||||
private Application apiApplication;
|
||||
private Token apiToken;
|
||||
private boolean submitAfterGettingToken;
|
||||
private ProgressDialog progressDialog;
|
||||
private Uri avatarUri;
|
||||
private File avatarFile;
|
||||
private HashSet<EditText> errorFields=new HashSet<>();
|
||||
private ElevationOnScrollListener onScrollListener;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -81,25 +85,30 @@ public class SignupFragment extends AppKitFragment{
|
||||
setRetainInstance(true);
|
||||
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
|
||||
createAppAndGetToken();
|
||||
setTitle(R.string.signup_title);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
|
||||
public View onCreateContentView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
|
||||
View view=inflater.inflate(R.layout.fragment_onboarding_signup, container, false);
|
||||
|
||||
TextView title=view.findViewById(R.id.title);
|
||||
TextView domain=view.findViewById(R.id.domain);
|
||||
displayName=view.findViewById(R.id.display_name);
|
||||
username=view.findViewById(R.id.username);
|
||||
email=view.findViewById(R.id.email);
|
||||
password=view.findViewById(R.id.password);
|
||||
avatar=view.findViewById(R.id.avatar);
|
||||
passwordConfirm=view.findViewById(R.id.password_confirm);
|
||||
reason=view.findViewById(R.id.reason);
|
||||
reasonExplain=view.findViewById(R.id.reason_explain);
|
||||
View avaWrap=view.findViewById(R.id.ava_wrap);
|
||||
|
||||
title.setText(getString(R.string.signup_title, instance.uri));
|
||||
displayNameWrap=view.findViewById(R.id.display_name_wrap);
|
||||
usernameWrap=view.findViewById(R.id.username_wrap);
|
||||
emailWrap=view.findViewById(R.id.email_wrap);
|
||||
passwordWrap=view.findViewById(R.id.password_wrap);
|
||||
passwordConfirmWrap=view.findViewById(R.id.password_confirm_wrap);
|
||||
reasonWrap=view.findViewById(R.id.reason_wrap);
|
||||
|
||||
domain.setText('@'+instance.uri);
|
||||
|
||||
username.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||
@@ -114,23 +123,20 @@ public class SignupFragment extends AppKitFragment{
|
||||
btn=view.findViewById(R.id.btn_next);
|
||||
btn.setOnClickListener(v->onButtonClick());
|
||||
buttonBar=view.findViewById(R.id.button_bar);
|
||||
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
|
||||
updateButtonState();
|
||||
|
||||
username.addTextChangedListener(buttonStateUpdater);
|
||||
email.addTextChangedListener(buttonStateUpdater);
|
||||
password.addTextChangedListener(buttonStateUpdater);
|
||||
passwordConfirm.addTextChangedListener(buttonStateUpdater);
|
||||
reason.addTextChangedListener(buttonStateUpdater);
|
||||
|
||||
username.addTextChangedListener(new ErrorClearingListener(username));
|
||||
email.addTextChangedListener(new ErrorClearingListener(email));
|
||||
password.addTextChangedListener(new ErrorClearingListener(password));
|
||||
passwordConfirm.addTextChangedListener(new ErrorClearingListener(passwordConfirm));
|
||||
reason.addTextChangedListener(new ErrorClearingListener(reason));
|
||||
|
||||
avaWrap.setOutlineProvider(OutlineProviders.roundedRect(22));
|
||||
avaWrap.setClipToOutline(true);
|
||||
avaWrap.setOnClickListener(v->onAvatarClick());
|
||||
|
||||
if(!instance.approvalRequired){
|
||||
reason.setVisibility(View.GONE);
|
||||
reasonExplain.setVisibility(View.GONE);
|
||||
@@ -142,10 +148,26 @@ public class SignupFragment extends AppKitFragment{
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
view.findViewById(R.id.scroller).setOnScrollChangeListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUpdateToolbar(){
|
||||
super.onUpdateToolbar();
|
||||
getToolbar().setBackgroundResource(R.drawable.bg_onboarding_panel);
|
||||
getToolbar().setElevation(0);
|
||||
if(onScrollListener!=null){
|
||||
onScrollListener.setViews(buttonBar, getToolbar());
|
||||
}
|
||||
}
|
||||
|
||||
private void onButtonClick(){
|
||||
if(!password.getText().toString().equals(passwordConfirm.getText().toString())){
|
||||
passwordConfirmWrap.setErrorState(getString(R.string.signup_passwords_dont_match));
|
||||
return;
|
||||
}
|
||||
showProgressDialog();
|
||||
if(currentBackgroundRequest!=null){
|
||||
submitAfterGettingToken=true;
|
||||
@@ -160,32 +182,8 @@ public class SignupFragment extends AppKitFragment{
|
||||
}
|
||||
}
|
||||
|
||||
private void copyAvatar(Runnable onDone){
|
||||
// Need to copy the avatar from the content provider to somewhere accessible in case the app gets killed between signup and account activation
|
||||
Activity activity=getActivity();
|
||||
MastodonAPIController.runInBackground(()->{
|
||||
String origName=UiUtils.getFileName(avatarUri);
|
||||
avatarFile=new File(activity.getCacheDir(), System.currentTimeMillis()+origName.substring(origName.lastIndexOf('.')));
|
||||
try(InputStream in=activity.getContentResolver().openInputStream(avatarUri);
|
||||
FileOutputStream out=new FileOutputStream(avatarFile)){
|
||||
byte[] buf=new byte[10240];
|
||||
int read;
|
||||
while((read=in.read(buf))>0){
|
||||
out.write(buf, 0, read);
|
||||
}
|
||||
}catch(IOException x){
|
||||
Log.w(TAG, "copyAvatar: error copying", x);
|
||||
}
|
||||
activity.runOnUiThread(onDone);
|
||||
});
|
||||
}
|
||||
|
||||
private void submit(){
|
||||
if(avatarUri!=null && (avatarFile==null || !avatarFile.exists())){
|
||||
copyAvatar(this::actuallySubmit);
|
||||
}else{
|
||||
actuallySubmit();
|
||||
}
|
||||
actuallySubmit();
|
||||
}
|
||||
|
||||
private void actuallySubmit(){
|
||||
@@ -204,9 +202,7 @@ public class SignupFragment extends AppKitFragment{
|
||||
fakeAccount.acct=fakeAccount.username=username;
|
||||
fakeAccount.id="tmp"+System.currentTimeMillis();
|
||||
fakeAccount.displayName=displayName.getText().toString();
|
||||
if(avatarFile!=null)
|
||||
fakeAccount.avatar=avatarFile.getAbsolutePath();
|
||||
AccountSessionManager.getInstance().addAccount(instance, result, fakeAccount, apiApplication, false);
|
||||
AccountSessionManager.getInstance().addAccount(instance, result, fakeAccount, apiApplication, new AccountActivationInfo(email, System.currentTimeMillis()));
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", AccountSessionManager.getInstance().getLastActiveAccountID());
|
||||
Nav.goClearingStack(getActivity(), AccountActivationFragment.class, args);
|
||||
@@ -224,7 +220,22 @@ public class SignupFragment extends AppKitFragment{
|
||||
anyFieldsSkipped=true;
|
||||
continue;
|
||||
}
|
||||
field.setError(fieldErrors.get(fieldName).stream().map(err->err.description).collect(Collectors.joining("\n")));
|
||||
List<MastodonDetailedErrorResponse.FieldError> errors=Objects.requireNonNull(fieldErrors.get(fieldName));
|
||||
if(errors.size()==1){
|
||||
getFieldWrapByName(fieldName).setErrorState(getErrorDescription(errors.get(0), fieldName));
|
||||
}else{
|
||||
SpannableStringBuilder ssb=new SpannableStringBuilder();
|
||||
boolean firstErr=true;
|
||||
for(MastodonDetailedErrorResponse.FieldError err:errors){
|
||||
if(firstErr){
|
||||
firstErr=false;
|
||||
}else{
|
||||
ssb.append('\n');
|
||||
}
|
||||
ssb.append(getErrorDescription(err, fieldName));
|
||||
}
|
||||
getFieldWrapByName(fieldName).setErrorState(getErrorDescription(errors.get(0), fieldName));
|
||||
}
|
||||
errorFields.add(field);
|
||||
if(first){
|
||||
first=false;
|
||||
@@ -242,6 +253,40 @@ public class SignupFragment extends AppKitFragment{
|
||||
.exec(instance.uri, apiToken);
|
||||
}
|
||||
|
||||
private CharSequence getErrorDescription(MastodonDetailedErrorResponse.FieldError error, String fieldName){
|
||||
return switch(fieldName){
|
||||
case "email" -> switch(error.error){
|
||||
case "ERR_BLOCKED" -> {
|
||||
String emailAddr=email.getText().toString();
|
||||
String s=getResources().getString(R.string.signup_email_domain_blocked, TextUtils.htmlEncode(instance.uri), TextUtils.htmlEncode(emailAddr.substring(emailAddr.lastIndexOf('@')+1)));
|
||||
SpannableStringBuilder ssb=new SpannableStringBuilder();
|
||||
Jsoup.parseBodyFragment(s).body().traverse(new NodeVisitor(){
|
||||
private int spanStart;
|
||||
@Override
|
||||
public void head(Node node, int depth){
|
||||
if(node instanceof TextNode tn){
|
||||
ssb.append(tn.text());
|
||||
}else if(node instanceof Element){
|
||||
spanStart=ssb.length();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tail(Node node, int depth){
|
||||
if(node instanceof Element){
|
||||
ssb.setSpan(new LinkSpan("", SignupFragment.this::onGoBackLinkClick, LinkSpan.Type.CUSTOM, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
ssb.setSpan(new TypefaceSpan("sans-serif-medium"), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
});
|
||||
yield ssb;
|
||||
}
|
||||
default -> error.description;
|
||||
};
|
||||
default -> error.description;
|
||||
};
|
||||
}
|
||||
|
||||
private EditText getFieldByName(String name){
|
||||
return switch(name){
|
||||
case "email" -> email;
|
||||
@@ -252,6 +297,16 @@ public class SignupFragment extends AppKitFragment{
|
||||
};
|
||||
}
|
||||
|
||||
private FloatingHintEditTextLayout getFieldWrapByName(String name){
|
||||
return switch(name){
|
||||
case "email" -> emailWrap;
|
||||
case "username" -> usernameWrap;
|
||||
case "password" -> passwordWrap;
|
||||
case "reason" -> reasonWrap;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private void showProgressDialog(){
|
||||
if(progressDialog==null){
|
||||
progressDialog=new ProgressDialog(getActivity());
|
||||
@@ -262,7 +317,7 @@ public class SignupFragment extends AppKitFragment{
|
||||
}
|
||||
|
||||
private void updateButtonState(){
|
||||
btn.setEnabled(username.length()>0 && email.length()>0 && email.getText().toString().contains("@") && password.length()>=8 && (!instance.approvalRequired || reason.length()>0));
|
||||
btn.setEnabled(username.length()>0 && email.length()>0 && email.getText().toString().contains("@") && password.length()>=8 && passwordConfirm.length()>=8 && (!instance.approvalRequired || reason.length()>0));
|
||||
}
|
||||
|
||||
private void createAppAndGetToken(){
|
||||
@@ -324,18 +379,9 @@ public class SignupFragment extends AppKitFragment{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data){
|
||||
if(requestCode==AVATAR_RESULT && resultCode==Activity.RESULT_OK){
|
||||
avatarUri=data.getData();
|
||||
if(avatarFile!=null && avatarFile.exists())
|
||||
avatarFile.delete();
|
||||
ViewImageLoader.load(avatar, getResources().getDrawable(R.drawable.default_avatar), new UrlImageLoaderRequest(avatarUri, V.dp(100), V.dp(100)));
|
||||
}
|
||||
}
|
||||
|
||||
private void onAvatarClick(){
|
||||
startActivityForResult(new Intent(Intent.ACTION_GET_CONTENT).setType("image/*").addCategory(Intent.CATEGORY_OPENABLE), AVATAR_RESULT);
|
||||
private void onGoBackLinkClick(LinkSpan span){
|
||||
setResult(false, null);
|
||||
Nav.finish(this);
|
||||
}
|
||||
|
||||
private class ErrorClearingListener implements TextWatcher{
|
||||
|
||||
@@ -45,26 +45,26 @@ public class Attachment extends BaseModel{
|
||||
|
||||
public int getWidth(){
|
||||
if(meta==null)
|
||||
return 0;
|
||||
return 1920;
|
||||
if(meta.width>0)
|
||||
return meta.width;
|
||||
if(meta.original!=null && meta.original.width>0)
|
||||
return meta.original.width;
|
||||
if(meta.small!=null && meta.small.width>0)
|
||||
return meta.small.width;
|
||||
return 0;
|
||||
return 1920;
|
||||
}
|
||||
|
||||
public int getHeight(){
|
||||
if(meta==null)
|
||||
return 0;
|
||||
return 1080;
|
||||
if(meta.height>0)
|
||||
return meta.height;
|
||||
if(meta.original!=null && meta.original.height>0)
|
||||
return meta.original.height;
|
||||
if(meta.small!=null && meta.small.height>0)
|
||||
return meta.small.height;
|
||||
return 0;
|
||||
return 1080;
|
||||
}
|
||||
|
||||
public double getDuration(){
|
||||
|
||||
@@ -45,7 +45,7 @@ public class Instance extends BaseModel{
|
||||
@RequiredField
|
||||
public String version;
|
||||
/**
|
||||
* Primary langauges of the website and its staff.
|
||||
* Primary languages of the website and its staff.
|
||||
*/
|
||||
// @RequiredField
|
||||
public List<String> languages;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.joinmastodon.android.model;
|
||||
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class ParsedAccount{
|
||||
public Account account;
|
||||
public CharSequence parsedName, parsedBio;
|
||||
public CustomEmojiHelper emojiHelper;
|
||||
public ImageLoaderRequest avatarRequest;
|
||||
|
||||
public ParsedAccount(Account account, String accountID){
|
||||
this.account=account;
|
||||
parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
|
||||
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
||||
|
||||
emojiHelper=new CustomEmojiHelper();
|
||||
SpannableStringBuilder ssb=new SpannableStringBuilder(parsedName);
|
||||
ssb.append(parsedBio);
|
||||
emojiHelper.setText(ssb);
|
||||
|
||||
avatarRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(40), V.dp(40));
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ public class PushSubscription extends BaseModel implements Cloneable{
|
||||
", endpoint='"+endpoint+'\''+
|
||||
", alerts="+alerts+
|
||||
", serverKey='"+serverKey+'\''+
|
||||
", policy="+policy+
|
||||
'}';
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,10 @@ public class Relationship extends BaseModel{
|
||||
public boolean blockedBy;
|
||||
public String note;
|
||||
|
||||
public boolean canFollow(){
|
||||
return !(following || blocking || blockedBy || domainBlocking);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return "Relationship{"+
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package org.joinmastodon.android.model.catalog;
|
||||
|
||||
import android.graphics.Region;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import org.joinmastodon.android.api.AllFieldsAreRequired;
|
||||
import org.joinmastodon.android.api.ObjectValidationException;
|
||||
import org.joinmastodon.android.model.BaseModel;
|
||||
@@ -7,13 +12,17 @@ import org.joinmastodon.android.model.BaseModel;
|
||||
import java.net.IDN;
|
||||
import java.util.List;
|
||||
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
@AllFieldsAreRequired
|
||||
public class CatalogInstance extends BaseModel{
|
||||
public String domain;
|
||||
public String version;
|
||||
public String description;
|
||||
public List<String> languages;
|
||||
public String region;
|
||||
@SerializedName("region")
|
||||
private String _region;
|
||||
public List<String> categories;
|
||||
public String proxiedThumbnail;
|
||||
public int totalUsers;
|
||||
@@ -22,7 +31,9 @@ public class CatalogInstance extends BaseModel{
|
||||
public String language;
|
||||
public String category;
|
||||
|
||||
public transient Region region;
|
||||
public transient String normalizedDomain;
|
||||
public transient UrlImageLoaderRequest thumbnailRequest;
|
||||
|
||||
@Override
|
||||
public void postprocess() throws ObjectValidationException{
|
||||
@@ -31,6 +42,14 @@ public class CatalogInstance extends BaseModel{
|
||||
normalizedDomain=IDN.toUnicode(domain);
|
||||
else
|
||||
normalizedDomain=domain;
|
||||
if(!TextUtils.isEmpty(_region)){
|
||||
try{
|
||||
region=Region.valueOf(_region.toUpperCase());
|
||||
}catch(IllegalArgumentException ignore){}
|
||||
}
|
||||
if(!TextUtils.isEmpty(proxiedThumbnail)){
|
||||
thumbnailRequest=new UrlImageLoaderRequest(proxiedThumbnail, 0, V.dp(56));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -50,4 +69,13 @@ public class CatalogInstance extends BaseModel{
|
||||
", category='"+category+'\''+
|
||||
'}';
|
||||
}
|
||||
|
||||
public enum Region{
|
||||
EUROPE,
|
||||
NORTH_AMERICA,
|
||||
SOUTH_AMERICA,
|
||||
AFRICA,
|
||||
ASIA,
|
||||
OCEANIA
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,7 +243,10 @@ public class AccountSwitcherSheet extends BottomSheet{
|
||||
|
||||
public WrappedAccount(AccountSession session){
|
||||
this.session=session;
|
||||
req=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? session.self.avatar : session.self.avatarStatic, V.dp(50), V.dp(50));
|
||||
if(session.self.avatar!=null)
|
||||
req=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? session.self.avatar : session.self.avatarStatic, V.dp(50), V.dp(50));
|
||||
else
|
||||
req=null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
@@ -154,11 +155,16 @@ public class ComposeAutocompleteViewController{
|
||||
}else if(mode==Mode.EMOJIS){
|
||||
String _text=text.substring(1); // remove ':'
|
||||
List<WrappedEmoji> oldList=emojis;
|
||||
emojis=AccountSessionManager.getInstance()
|
||||
List<Emoji> allEmojis = AccountSessionManager.getInstance()
|
||||
.getCustomEmojis(AccountSessionManager.getInstance().getAccount(accountID).domain)
|
||||
.stream()
|
||||
.flatMap(ec->ec.emojis.stream())
|
||||
.filter(e->e.visibleInPicker && e.shortcode.startsWith(_text))
|
||||
.filter(e->e.visibleInPicker)
|
||||
.collect(Collectors.toList());
|
||||
List<Emoji> startsWithSearch = allEmojis.stream().filter(e -> e.shortcode.toLowerCase().startsWith(_text.toLowerCase())).collect(Collectors.toList());
|
||||
emojis=Stream.concat(startsWithSearch.stream(), allEmojis.stream()
|
||||
.filter(e -> !startsWithSearch.contains(e))
|
||||
.filter(e -> e.shortcode.toLowerCase().contains(_text.toLowerCase())))
|
||||
.map(WrappedEmoji::new)
|
||||
.collect(Collectors.toList());
|
||||
UiUtils.updateList(oldList, emojis, list, emojisAdapter, (e1, e2)->e1.emoji.shortcode.equals(e2.emoji.shortcode));
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
package org.joinmastodon.android.ui.displayitems;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.app.Activity;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
@@ -10,6 +21,7 @@ import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
||||
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
|
||||
public class PhotoStatusDisplayItem extends ImageStatusDisplayItem{
|
||||
public PhotoStatusDisplayItem(String parentID, Status status, Attachment photo, BaseStatusListFragment parentFragment, int index, int totalPhotos, PhotoLayoutHelper.TiledLayoutResult tiledLayout, PhotoLayoutHelper.TiledLayoutResult.Tile thisTile){
|
||||
@@ -23,9 +35,110 @@ public class PhotoStatusDisplayItem extends ImageStatusDisplayItem{
|
||||
}
|
||||
|
||||
public static class Holder extends ImageStatusDisplayItem.Holder<PhotoStatusDisplayItem>{
|
||||
private final FrameLayout altTextWrapper;
|
||||
private final TextView altTextButton;
|
||||
private final View altTextScroller;
|
||||
private final ImageButton altTextClose;
|
||||
private final TextView altText;
|
||||
|
||||
private boolean altTextShown;
|
||||
private AnimatorSet currentAnim;
|
||||
|
||||
public Holder(Activity activity, ViewGroup parent){
|
||||
super(activity, R.layout.display_item_photo, parent);
|
||||
altTextWrapper=findViewById(R.id.alt_text_wrapper);
|
||||
altTextButton=findViewById(R.id.alt_button);
|
||||
altTextScroller=findViewById(R.id.alt_text_scroller);
|
||||
altTextClose=findViewById(R.id.alt_text_close);
|
||||
altText=findViewById(R.id.alt_text);
|
||||
|
||||
altTextButton.setOnClickListener(this::onShowHideClick);
|
||||
altTextClose.setOnClickListener(this::onShowHideClick);
|
||||
// altTextScroller.setNestedScrollingEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(ImageStatusDisplayItem item){
|
||||
super.onBind(item);
|
||||
altTextShown=false;
|
||||
if(currentAnim!=null)
|
||||
currentAnim.cancel();
|
||||
altTextScroller.setVisibility(View.GONE);
|
||||
altTextClose.setVisibility(View.GONE);
|
||||
altTextButton.setVisibility(View.VISIBLE);
|
||||
altTextButton.setAlpha(1f);
|
||||
if(TextUtils.isEmpty(item.attachment.description)){
|
||||
altTextWrapper.setVisibility(View.GONE);
|
||||
}else{
|
||||
altTextWrapper.setVisibility(View.VISIBLE);
|
||||
altText.setText(item.attachment.description);
|
||||
}
|
||||
}
|
||||
|
||||
private void onShowHideClick(View v){
|
||||
boolean show=v.getId()==R.id.alt_button;
|
||||
|
||||
if(altTextShown==show)
|
||||
return;
|
||||
if(currentAnim!=null)
|
||||
currentAnim.cancel();
|
||||
|
||||
altTextShown=show;
|
||||
if(show){
|
||||
altTextScroller.setVisibility(View.VISIBLE);
|
||||
altTextClose.setVisibility(View.VISIBLE);
|
||||
}else{
|
||||
altTextButton.setVisibility(View.VISIBLE);
|
||||
// Hide these views temporarily so FrameLayout measures correctly
|
||||
altTextScroller.setVisibility(View.GONE);
|
||||
altTextClose.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// This is the current size...
|
||||
int prevLeft=altTextWrapper.getLeft();
|
||||
int prevRight=altTextWrapper.getRight();
|
||||
int prevTop=altTextWrapper.getTop();
|
||||
altTextWrapper.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||
@Override
|
||||
public boolean onPreDraw(){
|
||||
altTextWrapper.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
|
||||
// ...and this is after the layout pass, right now the FrameLayout has its final size, but we animate that change
|
||||
if(!show){
|
||||
// Show these views again so they're visible for the duration of the animation.
|
||||
// No one would notice they were missing during measure/layout.
|
||||
altTextScroller.setVisibility(View.VISIBLE);
|
||||
altTextClose.setVisibility(View.VISIBLE);
|
||||
}
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
set.playTogether(
|
||||
ObjectAnimator.ofInt(altTextWrapper, "left", prevLeft, altTextWrapper.getLeft()),
|
||||
ObjectAnimator.ofInt(altTextWrapper, "right", prevRight, altTextWrapper.getRight()),
|
||||
ObjectAnimator.ofInt(altTextWrapper, "top", prevTop, altTextWrapper.getTop()),
|
||||
ObjectAnimator.ofFloat(altTextButton, View.ALPHA, show ? 1f : 0f, show ? 0f : 1f),
|
||||
ObjectAnimator.ofFloat(altTextScroller, View.ALPHA, show ? 0f : 1f, show ? 1f : 0f),
|
||||
ObjectAnimator.ofFloat(altTextClose, View.ALPHA, show ? 0f : 1f, show ? 1f : 0f)
|
||||
);
|
||||
set.setDuration(300);
|
||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
set.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
if(show){
|
||||
altTextButton.setVisibility(View.GONE);
|
||||
}else{
|
||||
altTextScroller.setVisibility(View.GONE);
|
||||
altTextClose.setVisibility(View.GONE);
|
||||
}
|
||||
currentAnim=null;
|
||||
}
|
||||
});
|
||||
set.start();
|
||||
currentAnim=set;
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -602,7 +602,7 @@ public class TabLayout extends HorizontalScrollView {
|
||||
* <p>If the tab indicator color is not {@code Color.TRANSPARENT}, the indicator will be wrapped
|
||||
* and tinted right before it is drawn by {@link SlidingTabIndicator#draw(Canvas)}. If you'd like
|
||||
* the inherent color or the tinted color of a custom drawable to be used, make sure this color is
|
||||
* set to {@code Color.TRANSPARENT} to avoid your color/tint being overriden.
|
||||
* set to {@code Color.TRANSPARENT} to avoid your color/tint being overridden.
|
||||
*
|
||||
* @param color color to use for the indicator
|
||||
* @attr ref com.google.android.material.R.styleable#TabLayout_tabIndicatorColor
|
||||
|
||||
@@ -35,6 +35,7 @@ public class LinkSpan extends CharacterStyle {
|
||||
case URL -> UiUtils.openURL(context, accountID, link);
|
||||
case MENTION -> UiUtils.openProfileByID(context, accountID, link);
|
||||
case HASHTAG -> UiUtils.openHashtagTimeline(context, accountID, link);
|
||||
case CUSTOM -> listener.onLinkClick(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +58,7 @@ public class LinkSpan extends CharacterStyle {
|
||||
public enum Type{
|
||||
URL,
|
||||
MENTION,
|
||||
HASHTAG
|
||||
HASHTAG,
|
||||
CUSTOM
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,13 @@ import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.os.ext.SdkExtensions;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
@@ -442,6 +446,32 @@ public class UiUtils{
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
public static void setRelationshipToActionButtonM3(Relationship relationship, Button button){
|
||||
boolean secondaryStyle;
|
||||
if(relationship.blocking){
|
||||
button.setText(R.string.button_blocked);
|
||||
secondaryStyle=true;
|
||||
}else if(relationship.blockedBy){
|
||||
button.setText(R.string.button_follow);
|
||||
secondaryStyle=false;
|
||||
}else if(relationship.requested){
|
||||
button.setText(R.string.button_follow_pending);
|
||||
secondaryStyle=true;
|
||||
}else if(!relationship.following){
|
||||
button.setText(relationship.followedBy ? R.string.follow_back : R.string.button_follow);
|
||||
secondaryStyle=false;
|
||||
}else{
|
||||
button.setText(R.string.button_following);
|
||||
secondaryStyle=true;
|
||||
}
|
||||
|
||||
button.setEnabled(!relationship.blockedBy);
|
||||
int styleRes=secondaryStyle ? R.style.Widget_Mastodon_M3_Button_Tonal : R.style.Widget_Mastodon_M3_Button_Filled;
|
||||
TypedArray ta=button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.background});
|
||||
button.setBackground(ta.getDrawable(0));
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
public static void performAccountAction(Activity activity, Account account, String accountID, Relationship relationship, Button button, Consumer<Boolean> progressCallback, Consumer<Relationship> resultCallback){
|
||||
if(relationship.blocking){
|
||||
confirmToggleBlockUser(activity, accountID, account, true, resultCallback);
|
||||
@@ -587,4 +617,84 @@ public class UiUtils{
|
||||
}
|
||||
launchWebBrowser(context, url);
|
||||
}
|
||||
|
||||
private static String getSystemProperty(String key){
|
||||
try{
|
||||
Class<?> props=Class.forName("android.os.SystemProperties");
|
||||
Method get=props.getMethod("get", String.class);
|
||||
return (String)get.invoke(null, key);
|
||||
}catch(Exception ignore){}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isMIUI(){
|
||||
return !TextUtils.isEmpty(getSystemProperty("ro.miui.ui.version.code"));
|
||||
}
|
||||
|
||||
public static int alphaBlendColors(int color1, int color2, float alpha){
|
||||
float alpha0=1f-alpha;
|
||||
int r=Math.round(((color1 >> 16) & 0xFF)*alpha0+((color2 >> 16) & 0xFF)*alpha);
|
||||
int g=Math.round(((color1 >> 8) & 0xFF)*alpha0+((color2 >> 8) & 0xFF)*alpha);
|
||||
int b=Math.round((color1 & 0xFF)*alpha0+(color2 & 0xFF)*alpha);
|
||||
return 0xFF000000 | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if Android platform photopicker is available on the device\
|
||||
*
|
||||
* @return whether the device supports photopicker intents.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
public static boolean isPhotoPickerAvailable(){
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.TIRAMISU){
|
||||
return true;
|
||||
}else if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.R){
|
||||
return SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R)>=2;
|
||||
}else
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
public static Intent getMediaPickerIntent(String[] mimeTypes, int maxCount){
|
||||
Intent intent;
|
||||
if(isPhotoPickerAvailable()){
|
||||
intent=new Intent(MediaStore.ACTION_PICK_IMAGES);
|
||||
if(maxCount>1)
|
||||
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, maxCount);
|
||||
}else{
|
||||
intent=new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
}
|
||||
if(mimeTypes.length>1){
|
||||
intent.setType("*/*");
|
||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
|
||||
}else if(mimeTypes.length==1){
|
||||
intent.setType(mimeTypes[0]);
|
||||
}else{
|
||||
intent.setType("*/*");
|
||||
}
|
||||
if(maxCount>1)
|
||||
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
|
||||
return intent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a View.OnClickListener to filter multiple clicks in succession.
|
||||
* Useful for buttons that perform some action that changes their state asynchronously.
|
||||
* @param l
|
||||
* @return
|
||||
*/
|
||||
public static View.OnClickListener rateLimitedClickListener(View.OnClickListener l){
|
||||
return new View.OnClickListener(){
|
||||
private long lastClickTime;
|
||||
|
||||
@Override
|
||||
public void onClick(View v){
|
||||
if(SystemClock.uptimeMillis()-lastClickTime>500L){
|
||||
lastClickTime=SystemClock.uptimeMillis();
|
||||
l.onClick(v);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.Button;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class FilterChipView extends Button{
|
||||
|
||||
private boolean currentlySelected;
|
||||
|
||||
public FilterChipView(Context context){
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public FilterChipView(Context context, AttributeSet attrs){
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public FilterChipView(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
setCompoundDrawablePadding(V.dp(8));
|
||||
setBackgroundResource(R.drawable.bg_filter_chip);
|
||||
setTextAppearance(R.style.m3_label_large);
|
||||
setTextColor(getResources().getColorStateList(R.color.filter_chip_text, context.getTheme()));
|
||||
setCompoundDrawableTintList(ColorStateList.valueOf(UiUtils.getThemeColor(context, R.attr.colorM3OnSurface)));
|
||||
updatePadding();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawableStateChanged(){
|
||||
super.drawableStateChanged();
|
||||
if(currentlySelected==isSelected())
|
||||
return;
|
||||
currentlySelected=isSelected();
|
||||
Drawable start=currentlySelected ? getResources().getDrawable(R.drawable.ic_baseline_check_18, getContext().getTheme()) : null;
|
||||
Drawable end=getCompoundDrawablesRelative()[2];
|
||||
setCompoundDrawablesRelativeWithIntrinsicBounds(start, null, end, null);
|
||||
updatePadding();
|
||||
}
|
||||
|
||||
private void updatePadding(){
|
||||
int vertical=V.dp(6);
|
||||
Drawable[] drawables=getCompoundDrawablesRelative();
|
||||
setPaddingRelative(V.dp(drawables[0]==null ? 16 : 8), vertical, V.dp(drawables[2]==null ? 16 : 8), vertical);
|
||||
}
|
||||
|
||||
public void setDrawableEnd(@DrawableRes int drawable){
|
||||
setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, drawable, 0);
|
||||
updatePadding();
|
||||
}
|
||||
}
|
||||
@@ -5,19 +5,35 @@ import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Region;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.InsetDrawable;
|
||||
import android.os.Build;
|
||||
import android.text.Editable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
@@ -28,6 +44,11 @@ public class FloatingHintEditTextLayout extends FrameLayout{
|
||||
private int offsetY;
|
||||
private boolean hintVisible;
|
||||
private Animator currentAnim;
|
||||
private float animProgress;
|
||||
private RectF tmpRect=new RectF();
|
||||
private ColorStateList labelColors, origHintColors;
|
||||
private boolean errorState;
|
||||
private TextView errorView;
|
||||
|
||||
public FloatingHintEditTextLayout(Context context){
|
||||
this(context, null);
|
||||
@@ -44,7 +65,9 @@ public class FloatingHintEditTextLayout extends FrameLayout{
|
||||
TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.FloatingHintEditTextLayout);
|
||||
labelTextSize=ta.getDimensionPixelSize(R.styleable.FloatingHintEditTextLayout_android_labelTextSize, V.dp(12));
|
||||
offsetY=ta.getDimensionPixelOffset(R.styleable.FloatingHintEditTextLayout_editTextOffsetY, 0);
|
||||
labelColors=ta.getColorStateList(R.styleable.FloatingHintEditTextLayout_labelTextColor);
|
||||
ta.recycle();
|
||||
setAddStatesFromChildren(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -58,13 +81,15 @@ public class FloatingHintEditTextLayout extends FrameLayout{
|
||||
|
||||
label=new TextView(getContext());
|
||||
label.setTextSize(TypedValue.COMPLEX_UNIT_PX, labelTextSize);
|
||||
label.setTextColor(edit.getHintTextColors());
|
||||
// label.setTextColor(labelColors==null ? edit.getHintTextColors() : labelColors);
|
||||
origHintColors=edit.getHintTextColors();
|
||||
label.setText(edit.getHint());
|
||||
label.setSingleLine();
|
||||
label.setPivotX(0f);
|
||||
label.setPivotY(0f);
|
||||
label.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
|
||||
LayoutParams lp=new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.TOP);
|
||||
lp.setMarginStart(edit.getPaddingStart());
|
||||
lp.setMarginStart(edit.getPaddingStart()+((LayoutParams)edit.getLayoutParams()).getMarginStart());
|
||||
addView(label, lp);
|
||||
|
||||
hintVisible=edit.getText().length()==0;
|
||||
@@ -72,9 +97,24 @@ public class FloatingHintEditTextLayout extends FrameLayout{
|
||||
label.setAlpha(0f);
|
||||
|
||||
edit.addTextChangedListener(new SimpleTextWatcher(this::onTextChanged));
|
||||
|
||||
errorView=new LinkedTextView(getContext());
|
||||
errorView.setTextAppearance(R.style.m3_body_small);
|
||||
errorView.setTextColor(UiUtils.getThemeColor(getContext(), R.attr.colorM3OnSurfaceVariant));
|
||||
errorView.setLinkTextColor(UiUtils.getThemeColor(getContext(), R.attr.colorM3Primary));
|
||||
errorView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
errorView.setPadding(V.dp(16), V.dp(4), V.dp(16), 0);
|
||||
errorView.setVisibility(View.GONE);
|
||||
addView(errorView);
|
||||
}
|
||||
|
||||
private void onTextChanged(Editable text){
|
||||
if(errorState){
|
||||
errorView.setVisibility(View.GONE);
|
||||
errorState=false;
|
||||
setForeground(getResources().getDrawable(R.drawable.bg_m3_outlined_text_field, getContext().getTheme()));
|
||||
refreshDrawableState();
|
||||
}
|
||||
boolean newHintVisible=text.length()==0;
|
||||
if(newHintVisible==hintVisible)
|
||||
return;
|
||||
@@ -83,42 +123,232 @@ public class FloatingHintEditTextLayout extends FrameLayout{
|
||||
hintVisible=newHintVisible;
|
||||
|
||||
label.setAlpha(1);
|
||||
float scale=edit.getLineHeight()/(float)label.getLineHeight();
|
||||
float transY=edit.getHeight()/2f-edit.getLineHeight()/2f+(edit.getTop()-label.getTop())-(label.getHeight()/2f-label.getLineHeight()/2f);
|
||||
|
||||
AnimatorSet anim=new AnimatorSet();
|
||||
if(hintVisible){
|
||||
anim.playTogether(
|
||||
ObjectAnimator.ofFloat(edit, TRANSLATION_Y, 0),
|
||||
ObjectAnimator.ofFloat(label, SCALE_X, scale),
|
||||
ObjectAnimator.ofFloat(label, SCALE_Y, scale),
|
||||
ObjectAnimator.ofFloat(label, TRANSLATION_Y, transY)
|
||||
);
|
||||
edit.setHintTextColor(0);
|
||||
}else{
|
||||
label.setScaleX(scale);
|
||||
label.setScaleY(scale);
|
||||
label.setTranslationY(transY);
|
||||
anim.playTogether(
|
||||
ObjectAnimator.ofFloat(edit, TRANSLATION_Y, offsetY),
|
||||
ObjectAnimator.ofFloat(label, SCALE_X, 1f),
|
||||
ObjectAnimator.ofFloat(label, SCALE_Y, 1f),
|
||||
ObjectAnimator.ofFloat(label, TRANSLATION_Y, 0f)
|
||||
);
|
||||
}
|
||||
anim.setDuration(150);
|
||||
anim.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
anim.start();
|
||||
anim.addListener(new AnimatorListenerAdapter(){
|
||||
edit.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
currentAnim=null;
|
||||
public boolean onPreDraw(){
|
||||
edit.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
|
||||
float scale=edit.getLineHeight()/(float)label.getLineHeight();
|
||||
float transY=edit.getHeight()/2f-edit.getLineHeight()/2f+(edit.getTop()-label.getTop())-(label.getHeight()/2f-label.getLineHeight()/2f);
|
||||
|
||||
AnimatorSet anim=new AnimatorSet();
|
||||
if(hintVisible){
|
||||
label.setAlpha(0);
|
||||
edit.setHintTextColor(label.getTextColors());
|
||||
anim.playTogether(
|
||||
ObjectAnimator.ofFloat(edit, TRANSLATION_Y, 0),
|
||||
ObjectAnimator.ofFloat(label, SCALE_X, scale),
|
||||
ObjectAnimator.ofFloat(label, SCALE_Y, scale),
|
||||
ObjectAnimator.ofFloat(label, TRANSLATION_Y, transY),
|
||||
ObjectAnimator.ofFloat(FloatingHintEditTextLayout.this, "animProgress", 0f)
|
||||
);
|
||||
edit.setHintTextColor(0);
|
||||
}else{
|
||||
label.setScaleX(scale);
|
||||
label.setScaleY(scale);
|
||||
label.setTranslationY(transY);
|
||||
anim.playTogether(
|
||||
ObjectAnimator.ofFloat(edit, TRANSLATION_Y, offsetY),
|
||||
ObjectAnimator.ofFloat(label, SCALE_X, 1f),
|
||||
ObjectAnimator.ofFloat(label, SCALE_Y, 1f),
|
||||
ObjectAnimator.ofFloat(label, TRANSLATION_Y, 0f),
|
||||
ObjectAnimator.ofFloat(FloatingHintEditTextLayout.this, "animProgress", 1f)
|
||||
);
|
||||
}
|
||||
anim.setDuration(150);
|
||||
anim.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
anim.start();
|
||||
anim.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
currentAnim=null;
|
||||
if(hintVisible){
|
||||
label.setAlpha(0);
|
||||
edit.setHintTextColor(origHintColors);
|
||||
}
|
||||
}
|
||||
});
|
||||
currentAnim=anim;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
currentAnim=anim;
|
||||
}
|
||||
|
||||
@Keep
|
||||
public void setAnimProgress(float progress){
|
||||
animProgress=progress;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Keep
|
||||
public float getAnimProgress(){
|
||||
return animProgress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawForeground(Canvas canvas){
|
||||
if(getForeground()!=null && animProgress>0){
|
||||
canvas.save();
|
||||
float width=(label.getWidth()+V.dp(8))*animProgress;
|
||||
float centerX=label.getLeft()+label.getWidth()/2f;
|
||||
tmpRect.set(centerX-width/2f, label.getTop(), centerX+width/2f, label.getBottom());
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O)
|
||||
canvas.clipOutRect(tmpRect);
|
||||
else
|
||||
canvas.clipRect(tmpRect, Region.Op.DIFFERENCE);
|
||||
super.onDrawForeground(canvas);
|
||||
canvas.restore();
|
||||
}else{
|
||||
super.onDrawForeground(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setForeground(Drawable foreground){
|
||||
super.setForeground(new PaddedForegroundDrawable(foreground));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable getForeground(){
|
||||
if(super.getForeground() instanceof PaddedForegroundDrawable pfd){
|
||||
return pfd.wrapped;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawableStateChanged(){
|
||||
super.drawableStateChanged();
|
||||
if(label==null || errorState)
|
||||
return;
|
||||
ColorStateList color=labelColors==null ? origHintColors : labelColors;
|
||||
label.setTextColor(color.getColorForState(getDrawableState(), 0xff00ff00));
|
||||
}
|
||||
|
||||
public void setErrorState(CharSequence error){
|
||||
if(errorState)
|
||||
return;
|
||||
errorState=true;
|
||||
setForeground(getResources().getDrawable(R.drawable.bg_m3_outlined_text_field_error, getContext().getTheme()));
|
||||
label.setTextColor(UiUtils.getThemeColor(getContext(), R.attr.colorM3Error));
|
||||
errorView.setVisibility(VISIBLE);
|
||||
errorView.setText(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
|
||||
if(errorView.getVisibility()!=GONE){
|
||||
int width=MeasureSpec.getSize(widthMeasureSpec)-getPaddingLeft()-getPaddingRight();
|
||||
LayoutParams editLP=(LayoutParams) edit.getLayoutParams();
|
||||
width-=editLP.leftMargin+editLP.rightMargin;
|
||||
errorView.measure(width | MeasureSpec.EXACTLY, MeasureSpec.UNSPECIFIED);
|
||||
LayoutParams lp=(LayoutParams) errorView.getLayoutParams();
|
||||
lp.width=width;
|
||||
lp.height=errorView.getMeasuredHeight();
|
||||
lp.gravity=Gravity.LEFT | Gravity.BOTTOM;
|
||||
lp.leftMargin=editLP.leftMargin;
|
||||
editLP.bottomMargin=errorView.getMeasuredHeight();
|
||||
}else{
|
||||
LayoutParams editLP=(LayoutParams) edit.getLayoutParams();
|
||||
editLP.bottomMargin=0;
|
||||
}
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
private class PaddedForegroundDrawable extends Drawable{
|
||||
private final Drawable wrapped;
|
||||
|
||||
private PaddedForegroundDrawable(Drawable wrapped){
|
||||
this.wrapped=wrapped;
|
||||
wrapped.setCallback(new Callback(){
|
||||
@Override
|
||||
public void invalidateDrawable(@NonNull Drawable who){
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when){
|
||||
scheduleSelf(what, when);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what){
|
||||
unscheduleSelf(what);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas){
|
||||
wrapped.draw(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha){
|
||||
wrapped.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable ColorFilter colorFilter){
|
||||
wrapped.setColorFilter(colorFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity(){
|
||||
return wrapped.getOpacity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setState(@NonNull int[] stateSet){
|
||||
return wrapped.setState(stateSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLayoutDirection(){
|
||||
return wrapped.getLayoutDirection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlpha(){
|
||||
return wrapped.getAlpha();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ColorFilter getColorFilter(){
|
||||
return wrapped.getColorFilter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStateful(){
|
||||
return wrapped.isStateful();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public int[] getState(){
|
||||
return wrapped.getState();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Drawable getCurrent(){
|
||||
return wrapped.getCurrent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyTheme(@NonNull Resources.Theme t){
|
||||
wrapped.applyTheme(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canApplyTheme(){
|
||||
return wrapped.canApplyTheme();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(@NonNull Rect bounds){
|
||||
super.onBoundsChange(bounds);
|
||||
int offset=V.dp(12);
|
||||
wrapped.setBounds(edit.getLeft()-offset, edit.getTop()-offset, edit.getRight()+offset, edit.getBottom()+offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
public class NestableScrollView extends ScrollView{
|
||||
private float downY, touchslop;
|
||||
private boolean didDisallow;
|
||||
|
||||
public NestableScrollView(Context context){
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public NestableScrollView(Context context, AttributeSet attrs){
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public NestableScrollView(Context context, AttributeSet attrs, int defStyleAttr){
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
public NestableScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init(){
|
||||
touchslop=ViewConfiguration.get(getContext()).getScaledTouchSlop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev){
|
||||
if(ev.getAction()==MotionEvent.ACTION_DOWN){
|
||||
if(canScrollVertically(-1) || canScrollVertically(1)){
|
||||
getParent().requestDisallowInterceptTouchEvent(true);
|
||||
didDisallow=true;
|
||||
}else{
|
||||
didDisallow=false;
|
||||
}
|
||||
downY=ev.getY();
|
||||
}else if(didDisallow && ev.getAction()==MotionEvent.ACTION_MOVE){
|
||||
if(Math.abs(downY-ev.getY())>=touchslop){
|
||||
if(!canScrollVertically((int)(downY-ev.getY()))){
|
||||
didDisallow=false;
|
||||
getParent().requestDisallowInterceptTouchEvent(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.onTouchEvent(ev);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package org.joinmastodon.android.utils;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
|
||||
public class ElevationOnScrollListener extends RecyclerView.OnScrollListener implements View.OnScrollChangeListener{
|
||||
private boolean isAtTop;
|
||||
private Animator currentPanelsAnim;
|
||||
private View[] views;
|
||||
private FragmentRootLinearLayout fragmentRootLayout;
|
||||
|
||||
public ElevationOnScrollListener(FragmentRootLinearLayout fragmentRootLayout, View... views){
|
||||
isAtTop=true;
|
||||
this.fragmentRootLayout=fragmentRootLayout;
|
||||
this.views=views;
|
||||
for(View v:views){
|
||||
Drawable bg=v.getBackground().mutate();
|
||||
v.setBackground(bg);
|
||||
if(bg instanceof LayerDrawable ld){
|
||||
Drawable overlay=ld.findDrawableByLayerId(R.id.color_overlay);
|
||||
if(overlay!=null){
|
||||
overlay.setAlpha(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setViews(View... views){
|
||||
List<View> oldViews=Arrays.asList(this.views);
|
||||
this.views=views;
|
||||
for(View v:views){
|
||||
if(oldViews.contains(v))
|
||||
continue;
|
||||
Drawable bg=v.getBackground().mutate();
|
||||
v.setBackground(bg);
|
||||
if(bg instanceof LayerDrawable ld){
|
||||
Drawable overlay=ld.findDrawableByLayerId(R.id.color_overlay);
|
||||
if(overlay!=null){
|
||||
overlay.setAlpha(isAtTop ? 0 : 20);
|
||||
}
|
||||
}
|
||||
v.setTranslationZ(isAtTop ? 0 : V.dp(3));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
|
||||
boolean newAtTop=recyclerView.getChildCount()==0 || (recyclerView.getChildAdapterPosition(recyclerView.getChildAt(0))==0 && recyclerView.getChildAt(0).getTop()==recyclerView.getPaddingTop());
|
||||
handleScroll(recyclerView.getContext(), newAtTop);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){
|
||||
handleScroll(v.getContext(), scrollY<=0);
|
||||
}
|
||||
|
||||
private void handleScroll(Context context, boolean newAtTop){
|
||||
if(newAtTop!=isAtTop){
|
||||
isAtTop=newAtTop;
|
||||
if(currentPanelsAnim!=null)
|
||||
currentPanelsAnim.cancel();
|
||||
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
ArrayList<Animator> anims=new ArrayList<>();
|
||||
for(View v:views){
|
||||
if(v.getBackground() instanceof LayerDrawable ld){
|
||||
Drawable overlay=ld.findDrawableByLayerId(R.id.color_overlay);
|
||||
if(overlay!=null){
|
||||
anims.add(ObjectAnimator.ofInt(overlay, "alpha", isAtTop ? 0 : 20));
|
||||
}
|
||||
}
|
||||
anims.add(ObjectAnimator.ofFloat(v, View.TRANSLATION_Z, isAtTop ? 0 : V.dp(3)));
|
||||
}
|
||||
if(fragmentRootLayout!=null){
|
||||
int color;
|
||||
if(isAtTop){
|
||||
color=UiUtils.getThemeColor(context, R.attr.colorM3Background);
|
||||
}else{
|
||||
color=UiUtils.alphaBlendColors(UiUtils.getThemeColor(context, R.attr.colorM3Background), UiUtils.getThemeColor(context, R.attr.colorM3Primary), 0.07843137f);
|
||||
}
|
||||
anims.add(ObjectAnimator.ofArgb(fragmentRootLayout, "statusBarColor", color));
|
||||
}
|
||||
set.playTogether(anims);
|
||||
set.setDuration(150);
|
||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
set.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
currentPanelsAnim=null;
|
||||
}
|
||||
});
|
||||
set.start();
|
||||
currentPanelsAnim=set;
|
||||
}
|
||||
}
|
||||
}
|
||||
5
mastodon/src/main/res/color/button_text_m3_text.xml
Normal file
5
mastodon/src/main/res/color/button_text_m3_text.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?colorM3Primary" android:state_enabled="true"/>
|
||||
<item android:color="?colorM3OnSurface" android:alpha="0.38"/>
|
||||
</selector>
|
||||
5
mastodon/src/main/res/color/button_text_m3_tonal.xml
Normal file
5
mastodon/src/main/res/color/button_text_m3_tonal.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?colorM3OnSecondaryContainer" android:state_enabled="true"/>
|
||||
<item android:color="?colorM3OnSurface" android:alpha="0.38"/>
|
||||
</selector>
|
||||
5
mastodon/src/main/res/color/filter_chip_text.xml
Normal file
5
mastodon/src/main/res/color/filter_chip_text.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?colorM3OnSecondaryContainer" android:state_selected="true"/>
|
||||
<item android:color="?colorM3OnSurface"/>
|
||||
</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="?colorM3OnSecondaryContainer" android:alpha="0.12"/>
|
||||
</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="?colorM3OnSurfaceVariant" android:alpha="0.12"/>
|
||||
</selector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?colorM3Primary" android:state_focused="true"/>
|
||||
<item android:color="?colorM3OnSurfaceVariant"/>
|
||||
</selector>
|
||||
4
mastodon/src/main/res/color/m3_primary_overlay.xml
Normal file
4
mastodon/src/main/res/color/m3_primary_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="?colorM3Primary" android:alpha="0.12"/>
|
||||
</selector>
|
||||
9
mastodon/src/main/res/drawable/bg_button_m3_text.xml
Normal file
9
mastodon/src/main/res/drawable/bg_button_m3_text.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple android:color="@color/m3_primary_overlay" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@android:id/mask">
|
||||
<shape>
|
||||
<solid android:color="#000"/>
|
||||
<corners android:radius="20dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
19
mastodon/src/main/res/drawable/bg_button_m3_tonal.xml
Normal file
19
mastodon/src/main/res/drawable/bg_button_m3_tonal.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_enabled="true">
|
||||
<ripple android:color="@color/m3_on_secondary_container_overlay">
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="?colorM3SecondaryContainer"/>
|
||||
<corners android:radius="20dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
</item>
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="?colorM3DisabledBackground"/>
|
||||
<corners android:radius="20dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
29
mastodon/src/main/res/drawable/bg_filter_chip.xml
Normal file
29
mastodon/src/main/res/drawable/bg_filter_chip.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_selected="true">
|
||||
<ripple android:color="@color/m3_on_secondary_container_overlay">
|
||||
<item>
|
||||
<shape>
|
||||
<corners android:radius="8dp"/>
|
||||
<solid android:color="?colorM3SecondaryContainer"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
</item>
|
||||
<item>
|
||||
<ripple android:color="@color/m3_on_surface_variant_overlay">
|
||||
<item android:id="@android:id/mask">
|
||||
<shape>
|
||||
<corners android:radius="8dp"/>
|
||||
<solid android:color="#000"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape>
|
||||
<corners android:radius="8dp"/>
|
||||
<stroke android:color="?colorM3Outline" android:width="1dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
</item>
|
||||
</selector>
|
||||
5
mastodon/src/main/res/drawable/bg_image_alt_overlay.xml
Normal file
5
mastodon/src/main/res/drawable/bg_image_alt_overlay.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="#D9000000"/>
|
||||
<corners android:radius="4dp"/>
|
||||
</shape>
|
||||
19
mastodon/src/main/res/drawable/bg_m3_outlined_text_field.xml
Normal file
19
mastodon/src/main/res/drawable/bg_m3_outlined_text_field.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:left="12dp" android:top="12dp" android:right="12dp" android:bottom="12dp">
|
||||
<selector>
|
||||
<item android:state_focused="true">
|
||||
<shape>
|
||||
<stroke android:color="?colorM3Primary" android:width="2dp"/>
|
||||
<corners android:radius="4dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape>
|
||||
<stroke android:color="?colorM3Outline" android:width="1dp"/>
|
||||
<corners android:radius="4dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:left="12dp" android:top="12dp" android:right="12dp" android:bottom="12dp">
|
||||
<shape>
|
||||
<stroke android:color="?colorM3Error" android:width="2dp"/>
|
||||
<corners android:radius="4dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<selector>
|
||||
<item android:state_focused="true">
|
||||
<shape>
|
||||
<stroke android:color="?colorM3Primary" android:width="2dp"/>
|
||||
<corners android:radius="4dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape>
|
||||
<stroke android:color="?colorM3Outline" android:width="1dp"/>
|
||||
<corners android:radius="4dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
</item>
|
||||
</layer-list>
|
||||
5
mastodon/src/main/res/drawable/bg_onboarding_avatar.xml
Normal file
5
mastodon/src/main/res/drawable/bg_onboarding_avatar.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="28dp"/>
|
||||
<solid android:color="?colorM3Surface"/>
|
||||
</shape>
|
||||
9
mastodon/src/main/res/drawable/bg_onboarding_panel.xml
Normal file
9
mastodon/src/main/res/drawable/bg_onboarding_panel.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<color android:color="?colorM3Background"/>
|
||||
</item>
|
||||
<item android:id="@+id/color_overlay">
|
||||
<color android:color="?colorM3Primary"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
5
mastodon/src/main/res/drawable/empty_8dp.xml
Normal file
5
mastodon/src/main/res/drawable/empty_8dp.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<size android:width="8dp"/>
|
||||
<solid android:color="#00000000"/>
|
||||
</shape>
|
||||
9
mastodon/src/main/res/drawable/ic_add_24px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_add_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="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M11,19V13H5V11H11V5H13V11H19V13H13V19Z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M29.45,6V9H9Q9,9 9,9Q9,9 9,9V39Q9,39 9,39Q9,39 9,39H39Q39,39 39,39Q39,39 39,39V18.6H42V39Q42,40.2 41.1,41.1Q40.2,42 39,42H9Q7.8,42 6.9,41.1Q6,40.2 6,39V9Q6,7.8 6.9,6.9Q7.8,6 9,6ZM38,6V10.05H42.05V13.05H38V17.1H35V13.05H30.95V10.05H35V6ZM12,33.9H36L28.8,24.3L22.45,32.65L17.75,26.45ZM9,9V14.55V18.6V39Q9,39 9,39Q9,39 9,39Q9,39 9,39Q9,39 9,39V9Q9,9 9,9Q9,9 9,9Z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<vector android:height="18dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="18dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M7,10l5,5 5,-5z"/>
|
||||
</vector>
|
||||
5
mastodon/src/main/res/drawable/ic_baseline_check_18.xml
Normal file
5
mastodon/src/main/res/drawable/ic_baseline_check_18.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="18dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="18dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
|
||||
</vector>
|
||||
5
mastodon/src/main/res/drawable/ic_baseline_close_24.xml
Normal file
5
mastodon/src/main/res/drawable/ic_baseline_close_24.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_delete_24px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_delete_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="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7,21Q6.175,21 5.588,20.413Q5,19.825 5,19V6H4V4H9V3H15V4H20V6H19V19Q19,19.825 18.413,20.413Q17.825,21 17,21ZM17,6H7V19Q7,19 7,19Q7,19 7,19H17Q17,19 17,19Q17,19 17,19ZM9,17H11V8H9ZM13,17H15V8H13ZM7,6V19Q7,19 7,19Q7,19 7,19Q7,19 7,19Q7,19 7,19Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_drag_handle_24px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_drag_handle_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="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M4,15V13H20V15ZM4,11V9H20V11Z"/>
|
||||
</vector>
|
||||
5
mastodon/src/main/res/drawable/ic_outline_email_24.xml
Normal file
5
mastodon/src/main/res/drawable/ic_outline_email_24.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M22,6c0,-1.1 -0.9,-2 -2,-2L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6zM20,6l-8,5 -8,-5h16zM20,18L4,18L4,8l8,5 8,-5v10z"/>
|
||||
</vector>
|
||||
5
mastodon/src/main/res/drawable/ic_outline_link_24.xml
Normal file
5
mastodon/src/main/res/drawable/ic_outline_link_24.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M17,7h-4v2h4c1.65,0 3,1.35 3,3s-1.35,3 -3,3h-4v2h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5zM11,15L7,15c-1.65,0 -3,-1.35 -3,-3s1.35,-3 3,-3h4L11,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-2zM8,11h8v2L8,13z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M2,17h20v2H2V17zM3.15,12.95L4,11.47l0.85,1.48l1.3,-0.75L5.3,10.72H7v-1.5H5.3l0.85,-1.47L4.85,7L4,8.47L3.15,7l-1.3,0.75L2.7,9.22H1v1.5h1.7L1.85,12.2L3.15,12.95zM9.85,12.2l1.3,0.75L12,11.47l0.85,1.48l1.3,-0.75l-0.85,-1.48H15v-1.5h-1.7l0.85,-1.47L12.85,7L12,8.47L11.15,7l-1.3,0.75l0.85,1.47H9v1.5h1.7L9.85,12.2zM23,9.22h-1.7l0.85,-1.47L20.85,7L20,8.47L19.15,7l-1.3,0.75l0.85,1.47H17v1.5h1.7l-0.85,1.48l1.3,0.75L20,11.47l0.85,1.48l1.3,-0.75l-0.85,-1.48H23V9.22z"/>
|
||||
</vector>
|
||||
5
mastodon/src/main/res/drawable/ic_outline_person_24.xml
Normal file
5
mastodon/src/main/res/drawable/ic_outline_person_24.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,6c1.1,0 2,0.9 2,2s-0.9,2 -2,2 -2,-0.9 -2,-2 0.9,-2 2,-2m0,10c2.7,0 5.8,1.29 6,2L6,18c0.23,-0.72 3.31,-2 6,-2m0,-12C9.79,4 8,5.79 8,8s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
|
||||
</vector>
|
||||
5
mastodon/src/main/res/drawable/round_rect.xml
Normal file
5
mastodon/src/main/res/drawable/round_rect.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="100dp"/>
|
||||
<solid android:color="#000"/>
|
||||
</shape>
|
||||
@@ -1,27 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="257dp"
|
||||
android:height="67dp"
|
||||
android:viewportWidth="313"
|
||||
android:viewportHeight="81">
|
||||
<path
|
||||
android:pathData="M72.95,18.45C71.82,9.95 64.5,3.24 55.85,1.95C54.38,1.73 48.85,0.93 36.02,0.93H35.92C23.09,0.93 20.34,1.73 18.87,1.95C10.44,3.22 2.76,9.23 0.88,17.84C-0,22.08 -0.1,26.78 0.07,31.09C0.31,37.27 0.36,43.43 0.91,49.6C1.29,53.69 1.95,57.74 2.9,61.73C4.68,69.11 11.86,75.25 18.9,77.75C26.43,80.35 34.53,80.79 42.29,79C43.14,78.79 43.98,78.56 44.82,78.29C46.71,77.68 48.92,77 50.55,75.81C50.57,75.8 50.59,75.77 50.6,75.75C50.61,75.72 50.62,75.7 50.62,75.66V69.7C50.62,69.7 50.62,69.65 50.6,69.62C50.6,69.6 50.57,69.58 50.55,69.56C50.53,69.55 50.5,69.54 50.48,69.53C50.45,69.53 50.43,69.53 50.41,69.53C45.43,70.73 40.33,71.34 35.23,71.33C26.43,71.33 24.06,67.09 23.39,65.34C22.85,63.82 22.5,62.22 22.36,60.61C22.36,60.59 22.36,60.57 22.37,60.54C22.37,60.52 22.39,60.49 22.42,60.48C22.44,60.47 22.46,60.46 22.49,60.44H22.57C27.46,61.64 32.48,62.25 37.51,62.25C38.72,62.25 39.92,62.25 41.14,62.21C46.19,62.06 51.52,61.81 56.51,60.82C56.63,60.8 56.76,60.77 56.87,60.75C64.72,59.21 72.19,54.42 72.95,42.27C72.97,41.79 73.04,37.25 73.04,36.76C73.04,35.07 73.58,24.79 72.96,18.48L72.95,18.45Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="36.62"
|
||||
android:startY="0.93"
|
||||
android:endX="36.62"
|
||||
android:endY="80.07"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FF6364FF"/>
|
||||
<item android:offset="1" android:color="#FF563ACC"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M14.81,23.2C14.81,20.72 16.77,18.72 19.2,18.72C21.62,18.72 23.58,20.73 23.58,23.2C23.58,25.67 21.62,27.68 19.2,27.68C16.77,27.68 14.81,25.67 14.81,23.2Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M80.02,27.06V47.66H72.03V27.67C72.03,23.45 70.3,21.32 66.83,21.32C63,21.32 61.07,23.87 61.07,28.87V39.82H53.14V28.87C53.14,23.84 51.24,21.32 47.38,21.32C43.92,21.32 42.18,23.45 42.18,27.67V47.65H34.21V27.06C34.21,22.86 35.25,19.51 37.35,17.03C39.53,14.54 42.37,13.29 45.89,13.29C49.97,13.29 53.07,14.9 55.11,18.11L57.11,21.52L59.1,18.11C61.14,14.91 64.23,13.29 68.32,13.29C71.84,13.29 74.69,14.55 76.86,17.03C78.96,19.51 80.01,22.83 80.01,27.06H80.02ZM107.49,37.3C109.15,35.51 109.93,33.29 109.93,30.59C109.93,27.89 109.14,25.65 107.49,23.94C105.91,22.15 103.89,21.3 101.45,21.3C99.02,21.3 97.01,22.15 95.41,23.94C93.83,25.65 93.04,27.89 93.04,30.59C93.04,33.29 93.83,35.53 95.41,37.3C97,39 99.02,39.87 101.45,39.87C103.89,39.87 105.9,39.02 107.49,37.3ZM109.93,14.12H117.8V47.06H109.93V43.18C107.55,46.41 104.26,48 99.99,48C95.71,48 92.42,46.36 89.5,43C86.64,39.64 85.18,35.48 85.18,30.61C85.18,25.74 86.65,21.65 89.5,18.29C92.43,14.93 95.92,13.23 99.99,13.23C104.06,13.23 107.55,14.81 109.93,18.02V14.14V14.12ZM144.26,29.97C146.58,31.76 147.73,34.25 147.67,37.41C147.67,40.77 146.52,43.41 144.14,45.24C141.76,47.03 138.89,47.94 135.41,47.94C129.13,47.94 124.87,45.3 122.61,40.11L129.43,35.96C130.34,38.78 132.35,40.25 135.41,40.25C138.22,40.25 139.62,39.33 139.62,37.42C139.62,36.03 137.79,34.78 134.07,33.8C132.66,33.41 131.5,33.01 130.6,32.68C129.31,32.16 128.22,31.56 127.31,30.83C125.05,29.04 123.9,26.68 123.9,23.65C123.9,20.42 124.99,17.85 127.19,16C129.45,14.09 132.19,13.18 135.48,13.18C140.73,13.18 144.56,15.48 147.07,20.16L140.37,24.1C139.4,21.86 137.74,20.74 135.48,20.74C133.11,20.74 131.95,21.65 131.95,23.44C131.95,24.83 133.78,26.08 137.5,27.06C140.37,27.72 142.63,28.7 144.26,29.97H144.27H144.26ZM169.26,22.27H162.37V35.98C162.37,37.63 162.98,38.63 164.15,39.08C165,39.4 166.71,39.47 169.27,39.34V47.05C163.98,47.71 160.14,47.17 157.88,45.41C155.62,43.7 154.53,40.53 154.53,36V22.27H149.23V14.1H154.53V7.46L162.39,4.89V14.12H169.29V22.29H169.27L169.26,22.27ZM194.34,37.1C195.92,35.4 196.71,33.22 196.71,30.58C196.71,27.94 195.92,25.78 194.34,24.05C192.74,22.35 190.79,21.48 188.42,21.48C186.04,21.48 184.09,22.33 182.49,24.05C180.97,25.84 180.18,28 180.18,30.58C180.18,33.16 180.97,35.31 182.49,37.1C184.08,38.81 186.04,39.67 188.42,39.67C190.79,39.67 192.74,38.82 194.34,37.1ZM176.96,42.96C173.85,39.6 172.32,35.52 172.32,30.58C172.32,25.63 173.85,21.62 176.96,18.26C180.07,14.9 183.91,13.19 188.42,13.19C192.92,13.19 196.77,14.9 199.87,18.26C202.97,21.62 204.57,25.77 204.57,30.58C204.57,35.39 202.97,39.6 199.87,42.96C196.76,46.32 192.98,47.96 188.42,47.96C183.85,47.96 180.06,46.32 176.96,42.96ZM230.86,37.29C232.45,35.5 233.24,33.28 233.24,30.58C233.24,27.87 232.45,25.63 230.86,23.93C229.28,22.14 227.26,21.29 224.82,21.29C222.39,21.29 220.37,22.14 218.73,23.93C217.14,25.63 216.35,27.87 216.35,30.58C216.35,33.28 217.14,35.52 218.73,37.29C220.38,38.99 222.45,39.86 224.82,39.86C227.2,39.86 229.27,39 230.86,37.29ZM233.24,0.92H241.11V47.05H233.24V43.17C230.93,46.39 227.63,47.99 223.36,47.99C219.09,47.99 215.75,46.35 212.8,42.98C209.93,39.62 208.48,35.47 208.48,30.6C208.48,25.73 209.95,21.64 212.8,18.28C215.72,14.92 219.26,13.22 223.36,13.22C227.45,13.22 230.93,14.8 233.24,18.01V0.93V0.92ZM268.74,37.07C270.32,35.36 271.12,33.18 271.12,30.54C271.12,27.9 270.32,25.74 268.74,24.01C267.15,22.31 265.21,21.45 262.82,21.45C260.43,21.45 258.5,22.3 256.9,24.01C255.37,25.8 254.58,27.96 254.58,30.54C254.58,33.12 255.37,35.28 256.9,37.07C258.48,38.77 260.44,39.64 262.82,39.64C265.2,39.64 267.14,38.78 268.74,37.07ZM251.36,42.92C248.26,39.56 246.73,35.48 246.73,30.54C246.73,25.6 248.25,21.58 251.36,18.22C254.47,14.86 258.32,13.15 262.82,13.15C267.32,13.15 271.18,14.86 274.27,18.22C277.38,21.58 278.97,25.73 278.97,30.54C278.97,35.35 277.38,39.56 274.27,42.92C271.16,46.28 267.38,47.93 262.82,47.93C258.26,47.93 254.46,46.28 251.36,42.92ZM313,26.78V47.01H305.14V27.84C305.14,25.66 304.59,24.01 303.48,22.77C302.45,21.65 300.98,21.07 299.09,21.07C294.65,21.07 292.39,23.77 292.39,29.24V47.03H284.53V14.1H292.39V17.81C294.28,14.71 297.28,13.19 301.47,13.19C304.82,13.19 307.57,14.37 309.71,16.81C311.91,19.24 313,22.54 313,26.82"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
||||
4
mastodon/src/main/res/drawable/white_circle.xml
Normal file
4
mastodon/src/main/res/drawable/white_circle.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||
<solid android:color="#fff"/>
|
||||
</shape>
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
@@ -10,4 +11,62 @@
|
||||
android:layout_gravity="center"
|
||||
android:scaleType="centerCrop"/>
|
||||
|
||||
<!-- This is hidden from screenreaders because that same alt text is set as content description on the ImageView -->
|
||||
<FrameLayout
|
||||
android:id="@+id/alt_text_wrapper"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|bottom"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:importantForAccessibility="noHideDescendants"
|
||||
android:background="@drawable/bg_image_alt_overlay">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/alt_button"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="22dp"
|
||||
android:textAppearance="@style/m3_label_large"
|
||||
android:textColor="#FFF"
|
||||
android:gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
android:text="ALT"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/alt_text_close"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="end|top"
|
||||
android:src="@drawable/ic_baseline_close_24"
|
||||
android:tint="#FFF"
|
||||
android:background="?android:selectableItemBackgroundBorderless"/>
|
||||
|
||||
<org.joinmastodon.android.ui.views.NestableScrollView
|
||||
android:id="@+id/alt_text_scroller"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="40dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/alt_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="#FFF"
|
||||
tools:text="Alt text goes here"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</org.joinmastodon.android.ui.views.NestableScrollView>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout>
|
||||
@@ -165,16 +165,16 @@
|
||||
android:background="?colorBackgroundLightest"
|
||||
android:elevation="2dp"
|
||||
android:outlineProvider="bounds"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layoutDirection="locale">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_media"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:background="?android:attr/actionBarItemBackground"
|
||||
android:padding="0px"
|
||||
android:tint="@color/compose_button"
|
||||
android:tintMode="src_in"
|
||||
@@ -184,10 +184,10 @@
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_poll"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:background="?android:attr/actionBarItemBackground"
|
||||
android:padding="0px"
|
||||
android:tint="@color/compose_button"
|
||||
android:tintMode="src_in"
|
||||
@@ -197,10 +197,10 @@
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_emoji"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:background="?android:attr/actionBarItemBackground"
|
||||
android:padding="0px"
|
||||
android:tint="@color/compose_button"
|
||||
android:tintMode="src_in"
|
||||
@@ -210,10 +210,10 @@
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_spoiler"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:background="?android:attr/actionBarItemBackground"
|
||||
android:padding="0px"
|
||||
android:tint="@color/compose_button"
|
||||
android:tintMode="src_in"
|
||||
@@ -223,10 +223,10 @@
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_visibility"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:background="?android:attr/actionBarItemBackground"
|
||||
android:padding="0px"
|
||||
android:tint="@color/compose_button"
|
||||
android:tintMode="src_in"
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<me.grishka.appkit.views.FragmentRootLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".fragments.onboarding.AccountActivationFragment">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:background="?colorBackgroundLight">
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -17,40 +18,78 @@
|
||||
android:orientation="vertical"
|
||||
android:clipChildren="false">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_headline_medium"
|
||||
android:minHeight="36dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/confirm_email_title"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:textAppearance="@style/m3_title_medium"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:layout_marginStart="56dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:text="@string/confirm_email_subtitle"/>
|
||||
|
||||
<Space
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="0px"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="230dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_margin="32dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:adjustViewBounds="true"
|
||||
android:src="@drawable/confirm_email_art"/>
|
||||
|
||||
<Space
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="0px"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<include layout="@layout/button_bar_activation" />
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_gravity="center_horizontal">
|
||||
|
||||
</me.grishka.appkit.views.FragmentRootLinearLayout>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_label_large"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:text="@string/confirm_email_didnt_get"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_resend"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="36dp"
|
||||
android:text="@string/resend"
|
||||
android:paddingTop="0dp"
|
||||
android:paddingBottom="0dp"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingRight="12dp"
|
||||
android:fontFeatureSettings="'tnum'"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_next"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:minWidth="145dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled"
|
||||
android:text="@string/open_email_app" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -5,14 +5,103 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/appkit_loader_root"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:background="?colorBackgroundLight">
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/top_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="8dp"
|
||||
android:background="@drawable/bg_onboarding_panel">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/search_edit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:elevation="0dp"
|
||||
android:inputType="textFilter|textUri"
|
||||
android:layout_toEndOf="@+id/btn_back"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:paddingTop="0dp"
|
||||
android:paddingBottom="0dp"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="40dp"
|
||||
android:drawableStart="@drawable/ic_m3_search"
|
||||
android:drawablePadding="8dp"
|
||||
android:drawableTint="?colorM3OnSurfaceVariant"
|
||||
android:gravity="center_vertical"
|
||||
android:textColorHint="?colorM3OnSurfaceVariant"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:background="@drawable/round_rect"
|
||||
android:backgroundTint="?colorM3SurfaceVariant"
|
||||
android:hint="@string/search_communities"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/focus_thing"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="1dp"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:importantForAccessibility="no"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_back"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:background="?android:selectableItemBackgroundBorderless"
|
||||
android:tint="?colorM3OnSurface"
|
||||
android:contentDescription="@string/back"
|
||||
android:src="@drawable/ic_arrow_back"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/clear"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:background="?android:selectableItemBackgroundBorderless"
|
||||
android:layout_alignEnd="@id/search_edit"
|
||||
android:layout_alignTop="@id/search_edit"
|
||||
android:layout_margin="12dp"
|
||||
android:contentDescription="@string/clear"
|
||||
android:tint="?colorM3OnSurfaceVariant"
|
||||
android:visibility="gone"
|
||||
android:src="@drawable/ic_m3_cancel"/>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/filters_scroll"
|
||||
android:layout_below="@id/search_edit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:scrollbars="none">
|
||||
<LinearLayout
|
||||
android:id="@+id/filters_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:showDividers="middle"
|
||||
android:divider="@drawable/empty_8dp">
|
||||
</LinearLayout>
|
||||
</HorizontalScrollView>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/appkit_loader_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0px"
|
||||
android:layout_weight="1">
|
||||
android:layout_weight="1"
|
||||
android:background="?colorM3Surface">
|
||||
|
||||
<include layout="@layout/loading"
|
||||
android:id="@+id/loading"/>
|
||||
@@ -33,32 +122,39 @@
|
||||
android:id="@+id/button_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?colorBackgroundLight"
|
||||
android:outlineProvider="bounds"
|
||||
android:orientation="horizontal"
|
||||
android:elevation="3dp">
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/bg_onboarding_panel">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textAppearance="@style/m3_body_small"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:text="@string/signup_random_server_explain"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_back"
|
||||
android:layout_width="wrap_content"
|
||||
android:id="@+id/btn_random_instance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:minWidth="145dp"
|
||||
style="?secondaryLargeButtonStyle"
|
||||
android:text="@string/back"/>
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1"/>
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Text"
|
||||
android:text="@string/pick_server_for_me"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_next"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:minWidth="145dp"
|
||||
style="?primaryLargeButtonStyle"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled"
|
||||
android:text="@string/next" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<me.grishka.appkit.views.FragmentRootLinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/appkit_loader_root"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:background="?android:colorBackground">
|
||||
|
||||
<include layout="@layout/appkit_toolbar"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/appkit_loader_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<include layout="@layout/loading"
|
||||
android:id="@+id/loading"/>
|
||||
|
||||
<ViewStub android:layout="?errorViewLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/error"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/content_stub"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/button_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:background="@drawable/bg_onboarding_panel">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_next"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Tonal"
|
||||
android:text="@string/follow_all"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_skip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled"
|
||||
android:text="@string/skip"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</me.grishka.appkit.views.FragmentRootLinearLayout>
|
||||
@@ -0,0 +1,164 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scroller"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginStart="56dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:text="@string/profile_setup_subtitle"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="144dp"
|
||||
android:foreground="@drawable/ic_add_photo_alternate_48px"
|
||||
android:foregroundGravity="center"
|
||||
android:foregroundTint="?colorM3OnSecondaryContainer"
|
||||
android:scaleType="centerCrop"
|
||||
android:background="?colorM3SecondaryContainer"/>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="104dp"
|
||||
android:layout_height="104dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="-44dp"
|
||||
android:background="@drawable/bg_onboarding_avatar">
|
||||
<ImageView
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="96dp"
|
||||
android:layout_height="96dp"
|
||||
android:layout_gravity="center"
|
||||
android:foreground="@drawable/ic_add_photo_alternate_48px"
|
||||
android:foregroundGravity="center"
|
||||
android:foregroundTint="?colorM3OnSecondaryContainer"
|
||||
android:scaleType="centerCrop"
|
||||
android:background="?colorM3SecondaryContainer"/>
|
||||
</FrameLayout>
|
||||
|
||||
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
|
||||
android:id="@+id/display_name_wrap"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="80dp"
|
||||
android:paddingTop="4dp"
|
||||
app:labelTextColor="@color/m3_outlined_text_field_label"
|
||||
android:foreground="@drawable/bg_m3_outlined_text_field">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/display_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:padding="16dp"
|
||||
android:background="@null"
|
||||
android:elevation="0dp"
|
||||
android:inputType="textPersonName|textCapWords"
|
||||
android:autofillHints="name"
|
||||
android:singleLine="true"
|
||||
android:hint="@string/display_name"/>
|
||||
|
||||
</org.joinmastodon.android.ui.views.FloatingHintEditTextLayout>
|
||||
|
||||
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
|
||||
android:id="@+id/bio_wrap"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="80dp"
|
||||
android:layout_marginTop="-8dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:paddingTop="4dp"
|
||||
app:labelTextColor="@color/m3_outlined_text_field_label"
|
||||
android:foreground="@drawable/bg_m3_outlined_text_field">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/bio"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:padding="16dp"
|
||||
android:background="@null"
|
||||
android:elevation="0dp"
|
||||
android:inputType="textMultiLine|textCapSentences"
|
||||
android:hint="@string/profile_bio"/>
|
||||
|
||||
</org.joinmastodon.android.ui.views.FloatingHintEditTextLayout>
|
||||
|
||||
<org.joinmastodon.android.ui.views.ReorderableLinearLayout
|
||||
android:id="@+id/profile_fields"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/add_row"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:drawableEnd="@drawable/ic_add_24px"
|
||||
android:drawableTint="?colorM3OnSurface"
|
||||
android:drawablePadding="16dp"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:stateListAnimator="@null"
|
||||
android:text="@string/profile_add_row"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:textAppearance="@style/m3_body_small"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:text="@string/profile_setup_explanation"/>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:background="@drawable/bg_onboarding_panel"
|
||||
android:id="@+id/button_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_next"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:minWidth="145dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled"
|
||||
android:text="@string/next" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<me.grishka.appkit.views.FragmentRootLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
@@ -8,41 +8,37 @@
|
||||
android:id="@+id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:background="?colorBackgroundLight"/>
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<LinearLayout
|
||||
android:background="@drawable/bg_onboarding_panel"
|
||||
android:id="@+id/button_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?colorBackgroundLight"
|
||||
android:outlineProvider="bounds"
|
||||
android:orientation="horizontal"
|
||||
android:elevation="3dp">
|
||||
android:orientation="vertical">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_back"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:minWidth="145dp"
|
||||
style="?secondaryLargeButtonStyle"
|
||||
android:text="@string/back"/>
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1"/>
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Text"
|
||||
android:text="@string/server_rules_disagree"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_next"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:minWidth="145dp"
|
||||
style="?primaryLargeButtonStyle"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled"
|
||||
android:text="@string/i_agree" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</me.grishka.appkit.views.FragmentRootLinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -1,15 +1,16 @@
|
||||
<?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:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scroller"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:background="?colorBackgroundLight">
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -17,68 +18,59 @@
|
||||
android:orientation="vertical"
|
||||
android:clipChildren="false">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
|
||||
android:id="@+id/display_name_wrap"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:textAppearance="@style/m3_headline_medium"
|
||||
android:minHeight="36dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:gravity="center_vertical"
|
||||
tools:text="@string/signup_title"/>
|
||||
android:minHeight="80dp"
|
||||
android:paddingTop="4dp"
|
||||
app:labelTextColor="@color/m3_outlined_text_field_label"
|
||||
android:foreground="@drawable/bg_m3_outlined_text_field">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/ava_wrap"
|
||||
android:layout_width="88dp"
|
||||
android:layout_height="88dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginBottom="24dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/avatar"
|
||||
<EditText
|
||||
android:id="@+id/display_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/default_avatar"/>
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginStart="56dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:padding="16dp"
|
||||
android:background="@null"
|
||||
android:elevation="0dp"
|
||||
android:inputType="textPersonName|textCapWords"
|
||||
android:autofillHints="name"
|
||||
android:singleLine="true"
|
||||
android:hint="@string/display_name"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="22dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:gravity="center"
|
||||
android:background="@color/gray_800t"
|
||||
android:textAppearance="@style/m3_label_large"
|
||||
android:textColor="#eee"
|
||||
android:text="@string/edit_photo"/>
|
||||
<View
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="start|top"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:backgroundTint="?colorM3OnSurfaceVariant"
|
||||
android:background="@drawable/ic_outline_person_24"/>
|
||||
|
||||
</FrameLayout>
|
||||
</org.joinmastodon.android.ui.views.FloatingHintEditTextLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/display_name"
|
||||
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
|
||||
android:id="@+id/username_wrap"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:inputType="textPersonName|textCapWords"
|
||||
android:autofillHints="name"
|
||||
android:singleLine="true"
|
||||
android:hint="@string/display_name"/>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp">
|
||||
android:layout_height="80dp"
|
||||
android:paddingTop="4dp"
|
||||
app:labelTextColor="@color/m3_outlined_text_field_label"
|
||||
android:foreground="@drawable/bg_m3_outlined_text_field">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginStart="56dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:padding="16dp"
|
||||
android:background="@null"
|
||||
android:elevation="0dp"
|
||||
android:inputType="textFilter|textNoSuggestions"
|
||||
android:autofillHints="username"
|
||||
android:singleLine="true"
|
||||
@@ -88,71 +80,165 @@
|
||||
<TextView
|
||||
android:id="@+id/domain"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="5dp"
|
||||
android:layout_gravity="right|center_vertical"
|
||||
android:layout_height="56dp"
|
||||
android:layout_gravity="right|top"
|
||||
android:layout_marginRight="20dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="16dp"
|
||||
android:textAppearance="@style/m3_title_medium"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:gravity="center_vertical"
|
||||
tools:text="\@mastodon.social"/>
|
||||
|
||||
</FrameLayout>
|
||||
</org.joinmastodon.android.ui.views.FloatingHintEditTextLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/email"
|
||||
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
|
||||
android:id="@+id/email_wrap"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:inputType="textEmailAddress"
|
||||
android:autofillHints="emailAddress"
|
||||
android:singleLine="true"
|
||||
android:hint="@string/email"/>
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="12dp"
|
||||
app:labelTextColor="@color/m3_outlined_text_field_label"
|
||||
android:foreground="@drawable/bg_m3_outlined_text_field">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/password"
|
||||
<EditText
|
||||
android:id="@+id/email"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginStart="56dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:padding="16dp"
|
||||
android:background="@null"
|
||||
android:elevation="0dp"
|
||||
android:inputType="textEmailAddress"
|
||||
android:autofillHints="emailAddress"
|
||||
android:singleLine="true"
|
||||
android:hint="@string/email"/>
|
||||
|
||||
<View
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="start|top"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:backgroundTint="?colorM3OnSurfaceVariant"
|
||||
android:background="@drawable/ic_outline_email_24"/>
|
||||
|
||||
</org.joinmastodon.android.ui.views.FloatingHintEditTextLayout>
|
||||
|
||||
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
|
||||
android:id="@+id/password_wrap"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:inputType="textPassword"
|
||||
android:autofillHints="password"
|
||||
android:singleLine="true"
|
||||
android:fontFamily="sans-serif"
|
||||
android:hint="@string/password"/>
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="12dp"
|
||||
app:labelTextColor="@color/m3_outlined_text_field_label"
|
||||
android:foreground="@drawable/bg_m3_outlined_text_field">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginStart="56dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:padding="16dp"
|
||||
android:background="@null"
|
||||
android:elevation="0dp"
|
||||
android:inputType="textPassword"
|
||||
android:autofillHints="password"
|
||||
android:singleLine="true"
|
||||
android:fontFamily="sans-serif"
|
||||
android:hint="@string/password"/>
|
||||
|
||||
<View
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="start|top"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:backgroundTint="?colorM3OnSurfaceVariant"
|
||||
android:background="@drawable/ic_outline_password_24"/>
|
||||
|
||||
</org.joinmastodon.android.ui.views.FloatingHintEditTextLayout>
|
||||
|
||||
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
|
||||
android:id="@+id/password_confirm_wrap"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="12dp"
|
||||
app:labelTextColor="@color/m3_outlined_text_field_label"
|
||||
android:foreground="@drawable/bg_m3_outlined_text_field">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/password_confirm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginStart="56dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:padding="16dp"
|
||||
android:background="@null"
|
||||
android:elevation="0dp"
|
||||
android:inputType="textPassword"
|
||||
android:autofillHints="password"
|
||||
android:singleLine="true"
|
||||
android:fontFamily="sans-serif"
|
||||
android:hint="@string/confirm_password"/>
|
||||
|
||||
</org.joinmastodon.android.ui.views.FloatingHintEditTextLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:layout_marginStart="56dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginTop="-8dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:textAppearance="@style/m3_body_small"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:text="@string/password_note"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/reason"
|
||||
|
||||
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
|
||||
android:id="@+id/reason_wrap"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:inputType="textCapSentences|textMultiLine"
|
||||
android:hint="@string/signup_reason"/>
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="12dp"
|
||||
app:labelTextColor="@color/m3_outlined_text_field_label"
|
||||
android:foreground="@drawable/bg_m3_outlined_text_field">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/reason"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="56dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:padding="16dp"
|
||||
android:background="@null"
|
||||
android:elevation="0dp"
|
||||
android:inputType="textCapSentences|textMultiLine"
|
||||
android:hint="@string/signup_reason"/>
|
||||
|
||||
</org.joinmastodon.android.ui.views.FloatingHintEditTextLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/reason_explain"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:layout_marginStart="56dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginTop="-8dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:textAppearance="@style/m3_body_small"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:text="@string/signup_reason_note"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -163,32 +249,19 @@
|
||||
android:id="@+id/button_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?colorBackgroundLight"
|
||||
android:outlineProvider="bounds"
|
||||
android:orientation="horizontal"
|
||||
android:elevation="3dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_back"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:minWidth="145dp"
|
||||
style="?secondaryLargeButtonStyle"
|
||||
android:text="@string/back"/>
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1"/>
|
||||
android:background="@drawable/bg_onboarding_panel">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_next"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:minWidth="145dp"
|
||||
style="?primaryLargeButtonStyle"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled"
|
||||
android:text="@string/next" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.joinmastodon.android.ui.views.SizeListenerFrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<org.joinmastodon.android.ui.views.SizeListenerFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
android:clipToPadding="false"
|
||||
android:clipChildren="false">
|
||||
android:clipToPadding="false">
|
||||
|
||||
<View
|
||||
android:id="@+id/blue_fill"
|
||||
@@ -18,51 +17,60 @@
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/art_container"
|
||||
android:layout_width="450dp"
|
||||
android:layout_height="631dp"
|
||||
android:layout_marginTop="40dp"
|
||||
android:layout_gravity="center">
|
||||
android:layout_width="360dp"
|
||||
android:layout_height="640dp"
|
||||
android:layout_gravity="center"
|
||||
tools:ignore="rtlHardcoded">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/art_clouds"
|
||||
android:layout_width="450dp"
|
||||
android:layout_height="589dp"
|
||||
android:layout_gravity="bottom|center_horizontal"
|
||||
android:layout_width="414dp"
|
||||
android:layout_height="541dp"
|
||||
android:layout_marginTop="91dp"
|
||||
android:layout_gravity="top|left"
|
||||
android:alpha="0.3"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/splash_art_layer0"/>
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/art_plane_elephant"
|
||||
android:layout_width="245.64dp"
|
||||
android:layout_height="72.65dp"
|
||||
android:layout_marginLeft="-101.55dp"
|
||||
android:layout_marginTop="238.12dp"
|
||||
android:layout_gravity="left|top"
|
||||
android:alpha="0.3"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/splash_art_layer4"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/art_right_hill"
|
||||
android:layout_width="218dp"
|
||||
android:layout_height="255dp"
|
||||
android:layout_gravity="bottom|right"
|
||||
android:layout_marginBottom="156dp"
|
||||
android:layout_marginRight="11dp"
|
||||
android:layout_width="150.84dp"
|
||||
android:layout_height="176.44dp"
|
||||
android:layout_gravity="top|left"
|
||||
android:layout_marginLeft="322dp"
|
||||
android:layout_marginTop="310dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/splash_art_layer1"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/art_left_hill"
|
||||
android:layout_width="285dp"
|
||||
android:layout_height="222dp"
|
||||
android:layout_gravity="bottom|left"
|
||||
android:layout_marginLeft="-6dp"
|
||||
android:layout_marginBottom="243dp"
|
||||
android:layout_width="197.2dp"
|
||||
android:layout_height="153.61dp"
|
||||
android:layout_gravity="top|left"
|
||||
android:layout_marginTop="294dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/splash_art_layer2"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/art_center_hill"
|
||||
android:layout_width="457dp"
|
||||
android:layout_height="397dp"
|
||||
android:layout_gravity="bottom|center_horizontal"
|
||||
android:layout_marginBottom="51dp"
|
||||
android:layout_width="400dp"
|
||||
android:layout_height="346dp"
|
||||
android:layout_gravity="top|left"
|
||||
android:layout_marginTop="294dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/splash_art_layer3"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/art_plane_elephant"
|
||||
android:layout_width="355dp"
|
||||
android:layout_height="105dp"
|
||||
android:layout_gravity="left|top"
|
||||
android:src="@drawable/splash_art_layer4"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<View
|
||||
@@ -73,36 +81,65 @@
|
||||
android:transformPivotY="1px"
|
||||
android:background="#478E6A"/>
|
||||
|
||||
<org.joinmastodon.android.ui.views.SplashLogoView
|
||||
android:layout_width="261dp"
|
||||
android:layout_height="71dp"
|
||||
android:layout_gravity="center_horizontal|top"
|
||||
android:layout_marginTop="24dp"
|
||||
android:scaleType="center"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/splash_logo"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:padding="16dp"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical">
|
||||
<Button
|
||||
android:id="@+id/btn_get_started"
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/pager_dots"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="13dp"
|
||||
style="@style/Widget.Mastodon.Button.Large.Primary_LightOnDark"
|
||||
android:text="@string/get_started"/>
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:layout_width="8dp"
|
||||
android:layout_height="8dp"
|
||||
android:background="@drawable/white_circle"/>
|
||||
|
||||
<View
|
||||
android:layout_width="8dp"
|
||||
android:layout_height="8dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:alpha="0.3"
|
||||
android:background="@drawable/white_circle"/>
|
||||
|
||||
<View
|
||||
android:layout_width="8dp"
|
||||
android:layout_height="8dp"
|
||||
android:alpha="0.3"
|
||||
android:background="@drawable/white_circle"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_log_in"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.Mastodon.Button.Large.Primary_DarkOnLight"
|
||||
android:background="@drawable/bg_button_green"
|
||||
android:text="@string/log_in"/>
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Text"
|
||||
android:textColor="#fff"
|
||||
android:text="@string/already_have_account"/>
|
||||
<Button
|
||||
android:id="@+id/btn_get_started"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled"
|
||||
android:text="@string/get_started"/>
|
||||
</LinearLayout>
|
||||
|
||||
</org.joinmastodon.android.ui.views.SizeListenerFrameLayout>
|
||||
@@ -1,61 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginLeft="19dp"
|
||||
android:layout_marginRight="19dp"
|
||||
android:textAppearance="@style/m3_headline_medium"
|
||||
android:minHeight="36dp"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/instance_catalog_title"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="19dp"
|
||||
android:layout_marginRight="19dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:textAppearance="@style/m3_title_medium"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:text="@string/instance_catalog_subtitle"/>
|
||||
|
||||
<org.joinmastodon.android.ui.tabs.TabLayout
|
||||
android:id="@+id/categories_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="72dp"
|
||||
android:background="@drawable/bg_catalog_tabs"
|
||||
app:tabIndicator="@drawable/mtrl_tabs_default_indicator"
|
||||
app:tabIndicatorAnimationMode="elastic"
|
||||
app:tabIndicatorColor="?android:textColorPrimary"
|
||||
app:tabMinWidth="120dp"
|
||||
app:tabMaxWidth="120dp"
|
||||
app:tabMode="scrollable"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/search_edit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textFilter|textNoSuggestions"
|
||||
android:singleLine="true"
|
||||
android:imeOptions="actionGo"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="19dp"
|
||||
android:layout_marginBottom="3dp"
|
||||
android:drawableStart="@drawable/ic_fluent_search_20_regular"
|
||||
android:drawablePadding="8dp"
|
||||
android:drawableTint="?android:textColorSecondary"
|
||||
android:hint="@string/search_communities"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -64,6 +64,7 @@
|
||||
android:contentDescription="@string/clear"
|
||||
android:stateListAnimator="@null"
|
||||
android:elevation="0dp"
|
||||
android:tint="?colorM3OnSurfaceVariant"
|
||||
android:src="@drawable/ic_m3_cancel"/>
|
||||
|
||||
</org.joinmastodon.android.ui.views.FloatingHintEditTextLayout>
|
||||
|
||||
@@ -3,17 +3,20 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp">
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radiobtn"
|
||||
android:layout_width="24dp"
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="14dp"
|
||||
android:layout_marginStart="-3dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:button="@drawable/ic_round_checkbox"
|
||||
android:buttonTint="?android:textColorSecondary"
|
||||
android:buttonTint="@color/m3_radiobutton_tint"
|
||||
android:background="@null"
|
||||
android:focusable="false"
|
||||
android:clickable="false"/>
|
||||
@@ -24,13 +27,13 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@id/radiobtn"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:textAppearance="@style/m3_title_medium"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:textSize="16sp"
|
||||
android:minHeight="24dp"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
tools:text="mastodon.social"/>
|
||||
|
||||
<TextView
|
||||
@@ -41,35 +44,9 @@
|
||||
android:layout_below="@id/title"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:textSize="14sp"
|
||||
android:lineSpacingExtra="4sp"
|
||||
tools:text="General-purpose server run by the lead developer of Mastodon"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/user_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@id/radiobtn"
|
||||
android:layout_below="@id/description"
|
||||
android:textAppearance="@style/m3_label_medium"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:drawableStart="@drawable/ic_fluent_people_community_16_regular"
|
||||
android:drawableTint="?android:textColorSecondary"
|
||||
android:drawablePadding="8dp"
|
||||
tools:text="588.8K"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/lang"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@id/user_count"
|
||||
android:layout_below="@id/description"
|
||||
android:layout_marginStart="24dp"
|
||||
android:textAppearance="@style/m3_label_medium"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:drawableStart="@drawable/ic_fluent_local_language_16_regular"
|
||||
android:drawableTint="?android:textColorSecondary"
|
||||
android:drawablePadding="8dp"
|
||||
tools:text="EN"/>
|
||||
|
||||
</RelativeLayout>
|
||||
12
mastodon/src/main/res/layout/item_list_header_simple.xml
Normal file
12
mastodon/src/main/res/layout/item_list_header_simple.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:paddingStart="56dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:paddingBottom="12dp">
|
||||
</TextView>
|
||||
@@ -3,42 +3,37 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:elevation="3dp"
|
||||
android:background="?colorBackgroundLightest"
|
||||
android:foreground="?android:selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/favicon"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:importantForAccessibility="no"
|
||||
tools:src="#0f0"/>
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="24dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/domain"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:layout_toEndOf="@id/favicon"
|
||||
android:layout_marginStart="4dp"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:textAppearance="@style/m3_title_small"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:gravity="center_vertical"
|
||||
tools:text="joinmastodon.org"/>
|
||||
<View
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:backgroundTint="?colorM3Primary"
|
||||
android:background="@drawable/ic_outline_link_24"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="24dp"
|
||||
android:layout_below="@id/domain"
|
||||
android:layout_marginTop="6dp"
|
||||
android:textAppearance="@style/m3_title_medium"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
tools:text="Mastodon for Android privacy policy"/>
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@id/icon"
|
||||
android:layout_marginStart="16dp"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:textColor="?colorM3Primary"
|
||||
tools:text="Privacy Policy - example.social"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@id/icon"
|
||||
android:layout_below="@id/title"
|
||||
android:layout_marginStart="16dp"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
tools:text="Privacy Policy - example.social"/>
|
||||
|
||||
</RelativeLayout>
|
||||
34
mastodon/src/main/res/layout/item_server_rule.xml
Normal file
34
mastodon/src/main/res/layout/item_server_rule.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:paddingStart="16dp"
|
||||
android:baselineAligned="false">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/number"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:textColor="?colorM3Primary"
|
||||
android:fontFamily="sans-serif-condensed"
|
||||
android:textStyle="bold"
|
||||
android:textSize="22dp"
|
||||
android:gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
tools:text="1"/>
|
||||
|
||||
<org.joinmastodon.android.ui.views.LinkedTextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
tools:text="No discrimination, including (but not limited to) racism, sexism, homophobia or transphobia."/>
|
||||
|
||||
</LinearLayout>
|
||||
94
mastodon/src/main/res/layout/item_user_row_m3.xml
Normal file
94
mastodon/src/main/res/layout/item_user_row_m3.xml
Normal file
@@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:importantForAccessibility="no"
|
||||
tools:src="#0f0"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/action_btn_wrap"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginTop="18dp"
|
||||
android:layout_marginStart="-8dp">
|
||||
|
||||
<org.joinmastodon.android.ui.views.ProgressBarButton
|
||||
android:id="@+id/action_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled"
|
||||
tools:text="Follow"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/action_progress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"
|
||||
style="?android:progressBarStyleSmall"
|
||||
android:elevation="10dp"
|
||||
android:outlineProvider="none"
|
||||
android:indeterminateTint="?colorM3OnPrimary"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="24dp"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:layout_toStartOf="@id/action_btn_wrap"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginTop="14dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:gravity="center_vertical"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:textAppearance="@style/m3_title_medium"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
tools:text="User Name"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:layout_below="@id/name"
|
||||
android:layout_toStartOf="@id/action_btn_wrap"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/m3_title_small"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
tools:text="\@username@server.social"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bio"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/avatar"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
tools:text="Description"/>
|
||||
|
||||
</RelativeLayout>
|
||||
62
mastodon/src/main/res/layout/onboarding_profile_field.xml
Normal file
62
mastodon/src/main/res/layout/onboarding_profile_field.xml
Normal file
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:clipToPadding="false"
|
||||
android:background="?colorM3Background">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/dragger_thingy"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:scaleType="center"
|
||||
android:tint="?colorM3OnSurface"
|
||||
android:contentDescription="@string/reorder"
|
||||
android:src="@drawable/ic_drag_handle_24px"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/delete"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginStart="8dp"
|
||||
android:scaleType="center"
|
||||
android:tint="?colorM3OnSurface"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:contentDescription="@string/delete"
|
||||
android:src="@drawable/ic_delete_24px"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_toEndOf="@id/dragger_thingy"
|
||||
android:layout_toStartOf="@id/delete"
|
||||
style="@style/Widget.Mastodon.M3.EditText"
|
||||
android:inputType="textCapSentences"
|
||||
android:hint="@string/field_content"
|
||||
android:saveEnabled="false"
|
||||
android:singleLine="true"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_toEndOf="@id/dragger_thingy"
|
||||
android:layout_toStartOf="@id/delete"
|
||||
android:layout_below="@id/content"
|
||||
style="@style/Widget.Mastodon.M3.EditText"
|
||||
android:inputType="textCapSentences"
|
||||
android:hint="@string/field_label"
|
||||
android:saveEnabled="false"
|
||||
android:singleLine="true"/>
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -37,6 +37,10 @@
|
||||
<attr name="colorM3Outline" format="color"/>
|
||||
<attr name="colorM3DisabledBackground" format="color"/>
|
||||
<attr name="colorM3PressedOverlay" format="color"/>
|
||||
<attr name="colorM3Error" format="color"/>
|
||||
<attr name="colorM3OnError" format="color"/>
|
||||
<attr name="colorM3ErrorContainer" format="color"/>
|
||||
<attr name="colorM3OnErrorContainer" format="color"/>
|
||||
|
||||
<attr name="primaryLargeButtonStyle" format="reference"/>
|
||||
<attr name="secondaryLargeButtonStyle" format="reference"/>
|
||||
@@ -48,5 +52,6 @@
|
||||
<declare-styleable name="FloatingHintEditTextLayout">
|
||||
<attr name="editTextOffsetY" format="dimension"/>
|
||||
<attr name="android:labelTextSize" format="dimension"/>
|
||||
<attr name="labelTextColor" format="color"/>
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
@@ -2,7 +2,8 @@
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">Mastodon</string>
|
||||
|
||||
<string name="get_started">Get started</string>
|
||||
<string name="get_started">Create account</string>
|
||||
<string name="already_have_account">I already have an account</string>
|
||||
<string name="log_in">Log in</string>
|
||||
<string name="next">Next</string>
|
||||
<string name="loading_instance">Retrieving server info…</string>
|
||||
@@ -178,15 +179,16 @@
|
||||
<string name="back">Back</string>
|
||||
<string name="instance_catalog_title">Mastodon is made of users on different servers.</string>
|
||||
<string name="instance_catalog_subtitle">Pick a server based on your interests, region, or a general purpose one. You can still connect with everyone, regardless of server.</string>
|
||||
<string name="search_communities">Search servers or enter URL</string>
|
||||
<string name="instance_rules_title">Some ground rules</string>
|
||||
<string name="instance_rules_subtitle">Take a minute to review the rules set and enforced by %s admins.</string>
|
||||
<string name="signup_title">Let\'s get you set up on %s</string>
|
||||
<string name="search_communities">Server name or URL</string>
|
||||
<string name="instance_rules_title">Server Rules</string>
|
||||
<string name="instance_rules_subtitle">By continuing, you agree to follow by the following rules set and enforced by the %s moderators.</string>
|
||||
<string name="signup_title">Create Account</string>
|
||||
<string name="edit_photo">edit</string>
|
||||
<string name="display_name">display name</string>
|
||||
<string name="username">username</string>
|
||||
<string name="email">email</string>
|
||||
<string name="password">password</string>
|
||||
<string name="display_name">Name</string>
|
||||
<string name="username">Username</string>
|
||||
<string name="email">Email</string>
|
||||
<string name="password">Password</string>
|
||||
<string name="confirm_password">Confirm password</string>
|
||||
<string name="password_note">Include capital letters, special characters, and numbers to increase your password strength.</string>
|
||||
<string name="category_academia">Academia</string>
|
||||
<string name="category_activism">Activism</string>
|
||||
@@ -201,8 +203,10 @@
|
||||
<string name="category_music">Music</string>
|
||||
<string name="category_regional">Regional</string>
|
||||
<string name="category_tech">Tech</string>
|
||||
<string name="confirm_email_title">One last thing</string>
|
||||
<string name="confirm_email_subtitle">Tap the link we emailed to you to verify your account.</string>
|
||||
<string name="confirm_email_title">Check Your Inbox</string>
|
||||
<!-- %s is the email address -->
|
||||
<string name="confirm_email_subtitle">Tap the link we sent you to verify %s. We\'ll wait right here.</string>
|
||||
<string name="confirm_email_didnt_get">Didn\'t get a link?</string>
|
||||
<string name="resend">Resend</string>
|
||||
<string name="open_email_app">Open email app</string>
|
||||
<string name="resent_email">Confirmation email sent</string>
|
||||
@@ -290,7 +294,7 @@
|
||||
<string name="open_in_browser">Open in browser</string>
|
||||
<string name="hide_boosts_from_user">Hide reblogs from %s</string>
|
||||
<string name="show_boosts_from_user">Show reblogs from %s</string>
|
||||
<string name="signup_reason">why do you want to join?</string>
|
||||
<string name="signup_reason">Why do you want to join?</string>
|
||||
<string name="signup_reason_note">This will help us review your application.</string>
|
||||
<string name="clear">Clear</string>
|
||||
<string name="profile_header">Header image</string>
|
||||
@@ -384,8 +388,8 @@
|
||||
<!-- %s is file size -->
|
||||
<string name="download_update">Download (%s)</string>
|
||||
<string name="install_update">Install</string>
|
||||
<string name="privacy_policy_title">Mastodon and your privacy</string>
|
||||
<string name="privacy_policy_subtitle">Although the Mastodon app does not collect any data, the server you sign up through may have a different policy. Take a minute to review and agree to the Mastodon app privacy policy and your server\'s privacy policy.</string>
|
||||
<string name="privacy_policy_title">Your Privacy</string>
|
||||
<string name="privacy_policy_subtitle">Although the Mastodon app does not collect any data, the server you sign up through may have a different policy.\n\nIf you disagree with the policy for %s, you can go back and pick a different server.</string>
|
||||
<string name="i_agree">I Agree</string>
|
||||
<string name="empty_list">This list is empty</string>
|
||||
<string name="instance_signup_closed">This server does not accept new registrations.</string>
|
||||
@@ -397,4 +401,41 @@
|
||||
<string name="login_title">Welcome Back</string>
|
||||
<string name="login_subtitle">Log in with the server where you created your account.</string>
|
||||
<string name="server_url">Server URL</string>
|
||||
<string name="welcome_page1_title">What is Mastodon?</string>
|
||||
<string name="welcome_page1_text">Imagine you have an email address that ends with @example.com.\n\nYou can still send and receive emails from anyone, even if their email ends in @gmail.com or @icloud.com or @example.com.</string>
|
||||
<string name="welcome_page2_title">Mastodon is like that.</string>
|
||||
<string name="welcome_page2_text">Your handle might be @gothgirl654@example.social, but you can still follow, reblog, and chat with @fallout5ever@example.online.</string>
|
||||
<string name="welcome_page3_title">How do I pick a server?</string>
|
||||
<string name="welcome_page3_text">Different people choose different servers for any number of reasons. art.example is a great place for artists, while glasgow.example might be a good pick for Scots.\n\nYou can’t go wrong with any of our recommend servers, so regardless of which one you pick (or if you enter your own in the server search bar), you’ll never miss a beat anywhere.</string>
|
||||
<string name="signup_random_server_explain">We\'ll pick a server based on your language if you continue without making a selection.</string>
|
||||
<string name="server_filter_any_language">Any Language</string>
|
||||
<string name="server_filter_instant_signup">Instant Sign-up</string>
|
||||
<string name="server_filter_manual_review">Manual Review</string>
|
||||
<string name="server_filter_any_signup_speed">Any Sign-up Speed</string>
|
||||
<string name="server_filter_region_europe">Europe</string>
|
||||
<string name="server_filter_region_north_america">North America</string>
|
||||
<string name="server_filter_region_south_america">South America</string>
|
||||
<string name="server_filter_region_africa">Africa</string>
|
||||
<string name="server_filter_region_asia">Asia</string>
|
||||
<string name="server_filter_region_oceania">Oceania</string>
|
||||
<string name="not_accepting_new_members">Not accepting new members</string>
|
||||
<string name="category_special_interests">Special Interests</string>
|
||||
<string name="signup_passwords_dont_match">Passwords don\'t match</string>
|
||||
<string name="pick_server_for_me">Pick for me</string>
|
||||
<string name="profile_add_row">Add row</string>
|
||||
<string name="profile_setup">Profile setup</string>
|
||||
<string name="profile_setup_subtitle">You can always complete this later in the Profile tab.</string>
|
||||
<string name="profile_setup_explanation">You can add up to four profile fields for anything you want. Location, links, pronouns — the sky is the limit.</string>
|
||||
<string name="popular_on_mastodon">Popular on Mastodon</string>
|
||||
<string name="follow_all">Follow all</string>
|
||||
<string name="server_rules_disagree">Disagree</string>
|
||||
<string name="privacy_policy_explanation">TL;DR: We don\'t collect or process anything.</string>
|
||||
<!-- %s is server domain -->
|
||||
<string name="server_policy_disagree">Disagree with %s</string>
|
||||
<string name="profile_bio">Bio</string>
|
||||
<!-- Shown in a progress dialog when you tap "follow all" -->
|
||||
<string name="sending_follows">Following users…</string>
|
||||
<!-- %1$s is server domain, %2$s is email domain. You can reorder these placeholders to fit your language better. -->
|
||||
<string name="signup_email_domain_blocked">%1$s doesn\'t allow signups from %2$s. Try a different one or <a>pick a different server</a>.</string>
|
||||
<string name="signup_username_taken">This username is taken.</string>
|
||||
</resources>
|
||||
@@ -66,6 +66,10 @@
|
||||
<item name="colorM3Outline">@color/m3_sys_light_outline</item>
|
||||
<item name="colorM3DisabledBackground">#1F1F1F1F</item>
|
||||
<item name="colorM3PressedOverlay">@color/m3_sys_light_on_primary</item>
|
||||
<item name="colorM3Error">#B3261E</item>
|
||||
<item name="colorM3OnError">#FFF</item>
|
||||
<item name="colorM3ErrorContainer">#F9DEDC</item>
|
||||
<item name="colorM3OnErrorContainer">#410E0B</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Mastodon.Dark" parent="Theme.AppKit">
|
||||
@@ -136,6 +140,10 @@
|
||||
<item name="colorM3Outline">@color/m3_sys_dark_outline</item>
|
||||
<item name="colorM3DisabledBackground">#1FE3E3E3</item>
|
||||
<item name="colorM3PressedOverlay">@color/m3_sys_dark_primary</item>
|
||||
<item name="colorM3Error">#F2B8B5</item>
|
||||
<item name="colorM3OnError">#601410</item>
|
||||
<item name="colorM3ErrorContainer">#8C1D18</item>
|
||||
<item name="colorM3OnErrorContainer">#F9DEDC</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Mastodon.Dark.TrueBlack">
|
||||
@@ -297,6 +305,32 @@
|
||||
<item name="android:paddingRight">24dp</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Mastodon.M3.Button.Text">
|
||||
<item name="android:background">@drawable/bg_button_m3_text</item>
|
||||
<item name="android:textColor">@color/button_text_m3_text</item>
|
||||
<item name="android:paddingLeft">24dp</item>
|
||||
<item name="android:paddingRight">24dp</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Mastodon.M3.Button.Tonal">
|
||||
<item name="android:background">@drawable/bg_button_m3_tonal</item>
|
||||
<item name="android:textColor">@color/button_text_m3_text</item>
|
||||
<item name="android:paddingLeft">24dp</item>
|
||||
<item name="android:paddingRight">24dp</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Mastodon.M3.EditText" parent="android:Widget.Material.EditText">
|
||||
<item name="android:background">@drawable/bg_m3_outlined_text_field_nopad</item>
|
||||
<item name="android:textAppearance">@style/m3_body_large</item>
|
||||
<item name="android:paddingLeft">16dp</item>
|
||||
<item name="android:paddingRight">16dp</item>
|
||||
<item name="android:paddingTop">8dp</item>
|
||||
<item name="android:paddingBottom">8dp</item>
|
||||
<item name="android:minHeight">40dp</item>
|
||||
<item name="android:textColorHint">?colorM3OnSurfaceVariant</item>
|
||||
<item name="android:textColor">?colorM3OnSurface</item>
|
||||
</style>
|
||||
|
||||
<style name="alert_title">
|
||||
<item name="android:textColor">?android:textColorPrimary</item>
|
||||
<item name="android:textSize">24dp</item>
|
||||
@@ -316,6 +350,12 @@
|
||||
<item name="android:lineSpacingExtra">4dp</item>
|
||||
</style>
|
||||
|
||||
<style name="m3_body_small">
|
||||
<item name="android:textSize">12dp</item>
|
||||
<item name="android:textColor">?android:textColorSecondary</item>
|
||||
<item name="android:lineSpacingExtra">2dp</item>
|
||||
</style>
|
||||
|
||||
<style name="m3_title_medium">
|
||||
<item name="android:fontFamily">sans-serif-medium</item>
|
||||
<item name="android:textSize">16dp</item>
|
||||
|
||||
@@ -1,35 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<locale android:name="ar-SA"/>
|
||||
<locale android:name="be-BY"/>
|
||||
<locale android:name="bn-BD"/>
|
||||
<locale android:name="bs-BA"/>
|
||||
<locale android:name="ca-ES"/>
|
||||
<locale android:name="cs-CZ"/>
|
||||
<locale android:name="da-DK"/>
|
||||
<locale android:name="de-DE"/>
|
||||
<locale android:name="el-GR"/>
|
||||
<locale android:name="en"/>
|
||||
<locale android:name="es-ES"/>
|
||||
<locale android:name="eu-ES"/>
|
||||
<locale android:name="fa-IR"/>
|
||||
<locale android:name="fi-FI"/>
|
||||
<locale android:name="fil-PH"/>
|
||||
<locale android:name="fr-FR"/>
|
||||
<locale android:name="ga-IE"/>
|
||||
<locale android:name="gd-GB"/>
|
||||
<locale android:name="gl-ES"/>
|
||||
<locale android:name="hi-IN"/>
|
||||
<locale android:name="hr-HR"/>
|
||||
<locale android:name="hu-HU"/>
|
||||
<locale android:name="hy-AM"/>
|
||||
<locale android:name="ig-NG"/>
|
||||
<locale android:name="in-ID"/>
|
||||
<locale android:name="is-IS"/>
|
||||
<locale android:name="it-IT"/>
|
||||
<locale android:name="iw-IL"/>
|
||||
<locale android:name="ja-JP"/>
|
||||
<locale android:name="kab"/>
|
||||
<locale android:name="ko-KR"/>
|
||||
<locale android:name="my-MM"/>
|
||||
<locale android:name="nl-NL"/>
|
||||
<locale android:name="no-NO"/>
|
||||
<locale android:name="oc-FR"/>
|
||||
<locale android:name="pl-PL"/>
|
||||
<locale android:name="pt-BR"/>
|
||||
<locale android:name="pt-PT"/>
|
||||
<locale android:name="ro-RO"/>
|
||||
<locale android:name="ru-RU"/>
|
||||
<locale android:name="si-LK"/>
|
||||
<locale android:name="sl-SI"/>
|
||||
<locale android:name="sv-SE"/>
|
||||
<locale android:name="th-TH"/>
|
||||
<locale android:name="tr-TR"/>
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
// Run: java tools/GenerateLocaleConfig.java
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import java.util.stream.*;
|
||||
|
||||
public class GenerateLocaleConfig{
|
||||
public static void main(String[] args) throws IOException{
|
||||
@@ -15,7 +12,7 @@ public class GenerateLocaleConfig{
|
||||
if(!dir.exists())
|
||||
throw new RuntimeException("Please run from project directory (can't find mastodon/src/main/res)");
|
||||
|
||||
ArrayList<String> locales=new ArrayList<>();
|
||||
ArrayList<String> locales=new ArrayList<>(), rawLocales=new ArrayList<>();
|
||||
locales.add("en");
|
||||
|
||||
for(File file:dir.listFiles()){
|
||||
@@ -23,11 +20,13 @@ public class GenerateLocaleConfig{
|
||||
if(file.isDirectory() && name.startsWith("values-")){
|
||||
if(new File(file, "strings.xml").exists()){
|
||||
locales.add(name.substring(name.indexOf('-')+1).replace("-r", "-"));
|
||||
rawLocales.add(name.substring(name.indexOf('-')+1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locales.sort(String::compareTo);
|
||||
rawLocales.sort(String::compareTo);
|
||||
try(OutputStreamWriter writer=new OutputStreamWriter(new FileOutputStream(new File(dir, "xml/locales_config.xml")), StandardCharsets.UTF_8)){
|
||||
writer.write("""
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
@@ -40,5 +39,25 @@ public class GenerateLocaleConfig{
|
||||
}
|
||||
writer.write("</locale-config>");
|
||||
}
|
||||
|
||||
File buildGradle=new File(dir, "../../../build.gradle");
|
||||
ArrayList<String> buildGradleLines=new ArrayList<>();
|
||||
try(BufferedReader reader=new BufferedReader(new InputStreamReader(new FileInputStream(buildGradle)))){
|
||||
String line;
|
||||
while((line=reader.readLine())!=null){
|
||||
if(line.trim().startsWith("resConfigs")){
|
||||
line=line.substring(0, line.indexOf('r'))+"resConfigs ";
|
||||
line+=rawLocales.stream().map(l->'"'+l+'"').collect(Collectors.joining(", "));
|
||||
}
|
||||
buildGradleLines.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
try(OutputStreamWriter writer=new OutputStreamWriter(new FileOutputStream(buildGradle))){
|
||||
for(String line:buildGradleLines){
|
||||
writer.write(line);
|
||||
writer.write('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user