Merge branch 'master' into #136
This commit is contained in:
commit
2b9f19805f
39 changed files with 525 additions and 648 deletions
|
@ -20,6 +20,7 @@ import android.content.Context;
|
|||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
@ -37,6 +38,7 @@ import android.support.v4.view.ViewCompat;
|
|||
import android.support.v4.view.ViewPager;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
@ -49,10 +51,9 @@ import com.keylesspalace.tusky.fragment.SFragment;
|
|||
import com.keylesspalace.tusky.interfaces.LinkListener;
|
||||
import com.keylesspalace.tusky.interfaces.StatusRemoveListener;
|
||||
import com.keylesspalace.tusky.pager.AccountPagerAdapter;
|
||||
import com.keylesspalace.tusky.receiver.TimelineReceiver;
|
||||
import com.keylesspalace.tusky.util.LinkHelper;
|
||||
import com.keylesspalace.tusky.util.Assert;
|
||||
import com.keylesspalace.tusky.util.Log;
|
||||
import com.keylesspalace.tusky.receiver.TimelineReceiver;
|
||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
import com.pkmmte.view.CircularImageView;
|
||||
import com.squareup.picasso.Picasso;
|
||||
|
@ -136,23 +137,17 @@ public class AccountActivity extends BaseActivity implements SFragment.OnUserRem
|
|||
@AttrRes int attribute;
|
||||
if (collapsingToolbar.getHeight() + verticalOffset
|
||||
< 2 * ViewCompat.getMinimumHeight(collapsingToolbar)) {
|
||||
if (getSupportActionBar() != null && loadedAccount != null) {
|
||||
getSupportActionBar().setTitle(loadedAccount.getDisplayName());
|
||||
|
||||
toolbar.setTitleTextColor(ThemeUtils.getColor(AccountActivity.this,
|
||||
android.R.attr.textColorPrimary));
|
||||
|
||||
String subtitle = String.format(getString(R.string.status_username_format),
|
||||
loadedAccount.username);
|
||||
getSupportActionBar().setSubtitle(subtitle);
|
||||
toolbar.setSubtitleTextColor(ThemeUtils.getColor(AccountActivity.this,
|
||||
android.R.attr.textColorSecondary));
|
||||
}
|
||||
|
||||
attribute = R.attr.account_toolbar_icon_tint_collapsed;
|
||||
} else {
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setTitle("");
|
||||
getSupportActionBar().setSubtitle("");
|
||||
}
|
||||
toolbar.setTitleTextColor(Color.TRANSPARENT);
|
||||
toolbar.setSubtitleTextColor(Color.TRANSPARENT);
|
||||
|
||||
attribute = R.attr.account_toolbar_icon_tint_uncollapsed;
|
||||
}
|
||||
if (attribute != priorAttribute) {
|
||||
|
@ -245,6 +240,15 @@ public class AccountActivity extends BaseActivity implements SFragment.OnUserRem
|
|||
|
||||
displayName.setText(account.getDisplayName());
|
||||
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setTitle(account.getDisplayName());
|
||||
|
||||
String subtitle = String.format(getString(R.string.status_username_format),
|
||||
account.username);
|
||||
getSupportActionBar().setSubtitle(subtitle);
|
||||
|
||||
}
|
||||
|
||||
boolean useCustomTabs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.getBoolean("customTabs", true);
|
||||
LinkHelper.setClickableText(note, account.note, null, useCustomTabs, new LinkListener() {
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
|
||||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
|
@ -24,23 +22,24 @@ import android.graphics.Color;
|
|||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.Spanned;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Menu;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.keylesspalace.tusky.entity.Session;
|
||||
import com.keylesspalace.tusky.json.SpannedTypeAdapter;
|
||||
import com.keylesspalace.tusky.json.StringWithEmoji;
|
||||
import com.keylesspalace.tusky.json.StringWithEmojiTypeAdapter;
|
||||
import com.keylesspalace.tusky.network.MastodonAPI;
|
||||
import com.keylesspalace.tusky.network.TuskyAPI;
|
||||
import com.keylesspalace.tusky.util.Log;
|
||||
import com.keylesspalace.tusky.network.TuskyApi;
|
||||
import com.keylesspalace.tusky.util.OkHttpUtils;
|
||||
import com.keylesspalace.tusky.util.PushNotificationClient;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -59,9 +58,9 @@ public class BaseActivity extends AppCompatActivity {
|
|||
private static final String TAG = "BaseActivity"; // logging tag
|
||||
|
||||
public MastodonAPI mastodonAPI;
|
||||
protected TuskyAPI tuskyAPI;
|
||||
public TuskyApi tuskyApi;
|
||||
protected PushNotificationClient pushNotificationClient;
|
||||
protected Dispatcher mastodonApiDispatcher;
|
||||
protected PendingIntent serviceAlarmIntent;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
|
@ -69,7 +68,8 @@ public class BaseActivity extends AppCompatActivity {
|
|||
|
||||
redirectIfNotLoggedIn();
|
||||
createMastodonAPI();
|
||||
createTuskyAPI();
|
||||
createTuskyApi();
|
||||
createPushNotificationClient();
|
||||
|
||||
/* There isn't presently a way to globally change the theme of a whole application at
|
||||
* runtime, just individual activities. So, each activity has to set its theme before any
|
||||
|
@ -161,15 +161,19 @@ public class BaseActivity extends AppCompatActivity {
|
|||
mastodonAPI = retrofit.create(MastodonAPI.class);
|
||||
}
|
||||
|
||||
protected void createTuskyAPI() {
|
||||
if (BuildConfig.USES_PUSH_NOTIFICATIONS) {
|
||||
Retrofit retrofit = new Retrofit.Builder()
|
||||
.baseUrl(getString(R.string.tusky_api_url))
|
||||
.client(OkHttpUtils.getCompatibleClient())
|
||||
.build();
|
||||
protected void createTuskyApi() {
|
||||
Retrofit retrofit = new Retrofit.Builder()
|
||||
.baseUrl("https://" + getString(R.string.tusky_api_url))
|
||||
.client(OkHttpUtils.getCompatibleClient())
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build();
|
||||
|
||||
tuskyAPI = retrofit.create(TuskyAPI.class);
|
||||
}
|
||||
tuskyApi = retrofit.create(TuskyApi.class);
|
||||
}
|
||||
|
||||
protected void createPushNotificationClient() {
|
||||
pushNotificationClient = new PushNotificationClient(getApplicationContext(),
|
||||
"ssl://" + getString(R.string.tusky_api_url) + ":8883");
|
||||
}
|
||||
|
||||
protected void redirectIfNotLoggedIn() {
|
||||
|
@ -203,49 +207,66 @@ public class BaseActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
protected void enablePushNotifications() {
|
||||
if (BuildConfig.USES_PUSH_NOTIFICATIONS) {
|
||||
String token = MessagingService.getInstanceToken();
|
||||
tuskyAPI.register(getBaseUrl(), getAccessToken(), token).enqueue(new Callback<ResponseBody>() {
|
||||
@Override
|
||||
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
|
||||
Log.d(TAG, "Enable push notifications response: " + response.message());
|
||||
Callback<ResponseBody> callback = new Callback<ResponseBody>() {
|
||||
@Override
|
||||
public void onResponse(Call<ResponseBody> call,
|
||||
retrofit2.Response<ResponseBody> response) {
|
||||
if (response.isSuccessful()) {
|
||||
pushNotificationClient.subscribeToTopic(getPushNotificationTopic());
|
||||
pushNotificationClient.connect(BaseActivity.this);
|
||||
} else {
|
||||
onEnablePushNotificationsFailure(response.message());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ResponseBody> call, Throwable t) {
|
||||
Log.d(TAG, "Enable push notifications failed: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Start up the MessagingService on a repeating interval for "pull" notifications.
|
||||
long checkInterval = 60 * 1000 * 5;
|
||||
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
|
||||
Intent intent = new Intent(this, MessagingService.class);
|
||||
final int SERVICE_REQUEST_CODE = 8574603; // This number is arbitrary.
|
||||
serviceAlarmIntent = PendingIntent.getService(this, SERVICE_REQUEST_CODE, intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
|
||||
SystemClock.elapsedRealtime(), checkInterval, serviceAlarmIntent);
|
||||
}
|
||||
@Override
|
||||
public void onFailure(Call<ResponseBody> call, Throwable t) {
|
||||
onEnablePushNotificationsFailure(t.getMessage());
|
||||
}
|
||||
};
|
||||
String deviceToken = pushNotificationClient.getDeviceToken();
|
||||
Session session = new Session(getDomain(), getAccessToken(), deviceToken);
|
||||
tuskyApi.register(session)
|
||||
.enqueue(callback);
|
||||
}
|
||||
|
||||
private void onEnablePushNotificationsFailure(String message) {
|
||||
Log.e(TAG, "Enabling push notifications failed. " + message);
|
||||
}
|
||||
|
||||
protected void disablePushNotifications() {
|
||||
if (BuildConfig.USES_PUSH_NOTIFICATIONS) {
|
||||
tuskyAPI.unregister(getBaseUrl(), getAccessToken()).enqueue(new Callback<ResponseBody>() {
|
||||
@Override
|
||||
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
|
||||
Log.d(TAG, "Disable push notifications response: " + response.message());
|
||||
Callback<ResponseBody> callback = new Callback<ResponseBody>() {
|
||||
@Override
|
||||
public void onResponse(Call<ResponseBody> call,
|
||||
retrofit2.Response<ResponseBody> response) {
|
||||
if (response.isSuccessful()) {
|
||||
pushNotificationClient.unsubscribeToTopic(getPushNotificationTopic());
|
||||
} else {
|
||||
onDisablePushNotificationsFailure();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ResponseBody> call, Throwable t) {
|
||||
Log.d(TAG, "Disable push notifications failed: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
} else if (serviceAlarmIntent != null) {
|
||||
// Cancel the repeating call for "pull" notifications.
|
||||
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
|
||||
alarmManager.cancel(serviceAlarmIntent);
|
||||
}
|
||||
@Override
|
||||
public void onFailure(Call<ResponseBody> call, Throwable t) {
|
||||
onDisablePushNotificationsFailure();
|
||||
}
|
||||
};
|
||||
String deviceToken = pushNotificationClient.getDeviceToken();
|
||||
Session session = new Session(getDomain(), getAccessToken(), deviceToken);
|
||||
tuskyApi.unregister(session)
|
||||
.enqueue(callback);
|
||||
}
|
||||
|
||||
private void onDisablePushNotificationsFailure() {
|
||||
Log.e(TAG, "Disabling push notifications failed.");
|
||||
}
|
||||
|
||||
private String getPushNotificationTopic() {
|
||||
return String.format("%s/%s/#", getDomain(), getAccessToken());
|
||||
}
|
||||
|
||||
private String getDomain() {
|
||||
return getPrivatePreferences()
|
||||
.getString("domain", null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ import android.text.Spanned;
|
|||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.style.URLSpan;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
@ -73,7 +74,6 @@ import com.keylesspalace.tusky.fragment.ComposeOptionsFragment;
|
|||
import com.keylesspalace.tusky.util.CountUpDownLatch;
|
||||
import com.keylesspalace.tusky.util.DownsizeImageTask;
|
||||
import com.keylesspalace.tusky.util.IOUtils;
|
||||
import com.keylesspalace.tusky.util.Log;
|
||||
import com.keylesspalace.tusky.util.MediaUtils;
|
||||
import com.keylesspalace.tusky.util.ParserUtils;
|
||||
import com.keylesspalace.tusky.util.SpanUtils;
|
||||
|
@ -252,7 +252,9 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFragm
|
|||
|
||||
if (replyVisibility != null && startingVisibility != null) {
|
||||
// Lowest possible visibility setting in response
|
||||
if (startingVisibility.equals("private") || replyVisibility.equals("private")) {
|
||||
if (startingVisibility.equals("direct") || replyVisibility.equals("direct")) {
|
||||
startingVisibility = "direct";
|
||||
} else if (startingVisibility.equals("private") || replyVisibility.equals("private")) {
|
||||
startingVisibility = "private";
|
||||
} else if (startingVisibility.equals("unlisted") || replyVisibility.equals("unlisted")) {
|
||||
startingVisibility = "unlisted";
|
||||
|
@ -637,6 +639,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFragm
|
|||
try {
|
||||
descriptor = getContentResolver().openAssetFileDescriptor(uri, "r");
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.d(TAG, Log.getStackTraceString(e));
|
||||
// Eat this exception, having the descriptor be null is sufficient.
|
||||
}
|
||||
if (descriptor != null) {
|
||||
|
@ -996,6 +999,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFragm
|
|||
try {
|
||||
stream = getContentResolver().openInputStream(item.uri);
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.d(TAG, Log.getStackTraceString(e));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ import android.support.v4.content.ContextCompat;
|
|||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
@ -44,7 +45,6 @@ import android.widget.ProgressBar;
|
|||
import com.keylesspalace.tusky.entity.Account;
|
||||
import com.keylesspalace.tusky.entity.Profile;
|
||||
import com.keylesspalace.tusky.util.IOUtils;
|
||||
import com.keylesspalace.tusky.util.Log;
|
||||
import com.pkmmte.view.CircularImageView;
|
||||
import com.squareup.picasso.Picasso;
|
||||
import com.theartofdev.edmodo.cropper.CropImage;
|
||||
|
@ -486,12 +486,14 @@ public class EditProfileActivity extends BaseActivity {
|
|||
try {
|
||||
inputStream = contentResolver.openInputStream(uri);
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.d(TAG, Log.getStackTraceString(e));
|
||||
return false;
|
||||
}
|
||||
Bitmap sourceBitmap;
|
||||
try {
|
||||
sourceBitmap = BitmapFactory.decodeStream(inputStream, null, null);
|
||||
} catch (OutOfMemoryError error) {
|
||||
Log.d(TAG, Log.getStackTraceString(error));
|
||||
return false;
|
||||
} finally {
|
||||
IOUtils.closeQuietly(inputStream);
|
||||
|
|
|
@ -29,6 +29,7 @@ import android.support.customtabs.CustomTabsIntent;
|
|||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
|
@ -39,7 +40,6 @@ import com.keylesspalace.tusky.entity.AccessToken;
|
|||
import com.keylesspalace.tusky.entity.AppCredentials;
|
||||
import com.keylesspalace.tusky.network.MastodonAPI;
|
||||
import com.keylesspalace.tusky.util.CustomTabsHelper;
|
||||
import com.keylesspalace.tusky.util.Log;
|
||||
import com.keylesspalace.tusky.util.OkHttpUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
@ -137,7 +137,7 @@ public class LoginActivity extends AppCompatActivity {
|
|||
s = s.replaceFirst("http://", "");
|
||||
s = s.replaceFirst("https://", "");
|
||||
// If a username was included (e.g. username@example.com), just take what's after the '@'.
|
||||
int at = s.indexOf('@');
|
||||
int at = s.lastIndexOf('@');
|
||||
if (at != -1) {
|
||||
s = s.substring(at + 1);
|
||||
}
|
||||
|
@ -201,7 +201,7 @@ public class LoginActivity extends AppCompatActivity {
|
|||
@Override
|
||||
public void onFailure(Call<AppCredentials> call, Throwable t) {
|
||||
editText.setError(getString(R.string.error_failed_app_registration));
|
||||
t.printStackTrace();
|
||||
Log.e(TAG, Log.getStackTraceString(t));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
|
||||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
|
@ -37,6 +36,7 @@ import android.text.SpannableStringBuilder;
|
|||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
@ -48,7 +48,6 @@ import com.keylesspalace.tusky.entity.Account;
|
|||
import com.keylesspalace.tusky.fragment.SFragment;
|
||||
import com.keylesspalace.tusky.interfaces.StatusRemoveListener;
|
||||
import com.keylesspalace.tusky.pager.TimelinePagerAdapter;
|
||||
import com.keylesspalace.tusky.util.Log;
|
||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
|
||||
import com.mikepenz.materialdrawer.AccountHeader;
|
||||
|
@ -215,8 +214,7 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
|
|||
.putString("current", "[]")
|
||||
.apply();
|
||||
|
||||
((NotificationManager) (getSystemService(NOTIFICATION_SERVICE)))
|
||||
.cancel(MessagingService.NOTIFY_ID);
|
||||
pushNotificationClient.clearNotifications(this);
|
||||
|
||||
/* After editing a profile, the profile header in the navigation drawer needs to be
|
||||
* refreshed */
|
||||
|
|
|
@ -25,6 +25,7 @@ import android.support.v7.widget.DividerItemDecoration;
|
|||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
@ -33,7 +34,6 @@ import android.widget.EditText;
|
|||
import com.keylesspalace.tusky.adapter.ReportAdapter;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.keylesspalace.tusky.util.HtmlUtils;
|
||||
import com.keylesspalace.tusky.util.Log;
|
||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/* Copyright 2017 Andrew Dawson
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.entity;
|
||||
|
||||
public class Session {
|
||||
public String instanceUrl;
|
||||
public String accessToken;
|
||||
public String deviceToken;
|
||||
|
||||
public Session(String instanceUrl, String accessToken, String deviceToken) {
|
||||
this.instanceUrl = instanceUrl;
|
||||
this.accessToken = accessToken;
|
||||
this.deviceToken = deviceToken;
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ import android.support.design.widget.TabLayout;
|
|||
import android.support.v7.widget.DividerItemDecoration;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
@ -42,9 +43,8 @@ import com.keylesspalace.tusky.entity.Relationship;
|
|||
import com.keylesspalace.tusky.interfaces.AccountActionListener;
|
||||
import com.keylesspalace.tusky.network.MastodonAPI;
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.view.EndlessOnScrollListener;
|
||||
import com.keylesspalace.tusky.util.Log;
|
||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
import com.keylesspalace.tusky.view.EndlessOnScrollListener;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import android.support.v4.widget.SwipeRefreshLayout;
|
|||
import android.support.v7.widget.DividerItemDecoration;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
@ -39,9 +40,8 @@ import com.keylesspalace.tusky.entity.Notification;
|
|||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
||||
import com.keylesspalace.tusky.interfaces.StatusRemoveListener;
|
||||
import com.keylesspalace.tusky.view.EndlessOnScrollListener;
|
||||
import com.keylesspalace.tusky.util.Log;
|
||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
import com.keylesspalace.tusky.view.EndlessOnScrollListener;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ import android.support.v4.widget.SwipeRefreshLayout;
|
|||
import android.support.v7.widget.DividerItemDecoration;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
@ -38,10 +39,9 @@ import com.keylesspalace.tusky.adapter.TimelineAdapter;
|
|||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
||||
import com.keylesspalace.tusky.interfaces.StatusRemoveListener;
|
||||
import com.keylesspalace.tusky.view.EndlessOnScrollListener;
|
||||
import com.keylesspalace.tusky.util.Log;
|
||||
import com.keylesspalace.tusky.receiver.TimelineReceiver;
|
||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
import com.keylesspalace.tusky.view.EndlessOnScrollListener;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
|
|
@ -25,6 +25,7 @@ import android.support.v4.widget.SwipeRefreshLayout;
|
|||
import android.support.v7.widget.DividerItemDecoration;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
@ -38,9 +39,8 @@ import com.keylesspalace.tusky.network.MastodonAPI;
|
|||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
||||
import com.keylesspalace.tusky.interfaces.StatusRemoveListener;
|
||||
import com.keylesspalace.tusky.view.ConversationLineItemDecoration;
|
||||
import com.keylesspalace.tusky.util.Log;
|
||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
import com.keylesspalace.tusky.view.ConversationLineItemDecoration;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
|
|
|
@ -15,17 +15,16 @@
|
|||
|
||||
package com.keylesspalace.tusky.network;
|
||||
|
||||
import com.keylesspalace.tusky.entity.Session;
|
||||
|
||||
import okhttp3.ResponseBody;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Field;
|
||||
import retrofit2.http.FormUrlEncoded;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.POST;
|
||||
|
||||
public interface TuskyAPI {
|
||||
@FormUrlEncoded
|
||||
public interface TuskyApi {
|
||||
@POST("/register")
|
||||
Call<ResponseBody> register(@Field("instance_url") String instanceUrl, @Field("access_token") String accessToken, @Field("device_token") String deviceToken);
|
||||
@FormUrlEncoded
|
||||
Call<ResponseBody> register(@Body Session session);
|
||||
@POST("/unregister")
|
||||
Call<ResponseBody> unregister(@Field("instance_url") String instanceUrl, @Field("access_token") String accessToken);
|
||||
Call<ResponseBody> unregister(@Body Session session);
|
||||
}
|
|
@ -9,6 +9,7 @@ import android.preference.PreferenceManager;
|
|||
import android.support.customtabs.CustomTabsIntent;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.text.style.URLSpan;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import com.keylesspalace.tusky.R;
|
||||
|
|
|
@ -23,6 +23,7 @@ import android.net.Uri;
|
|||
import android.os.AsyncTask;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.media.ExifInterface;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
|
@ -32,6 +33,7 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
|
||||
public class DownsizeImageTask extends AsyncTask<Uri, Void, Boolean> {
|
||||
private static final String TAG = "DownsizeImageTask";
|
||||
private int sizeLimit;
|
||||
private ContentResolver contentResolver;
|
||||
private Listener listener;
|
||||
|
@ -100,6 +102,7 @@ public class DownsizeImageTask extends AsyncTask<Uri, Void, Boolean> {
|
|||
try {
|
||||
inputStream = contentResolver.openInputStream(uri);
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.d(TAG, Log.getStackTraceString(e));
|
||||
return ExifInterface.ORIENTATION_UNDEFINED;
|
||||
}
|
||||
if (inputStream == null) {
|
||||
|
@ -109,6 +112,7 @@ public class DownsizeImageTask extends AsyncTask<Uri, Void, Boolean> {
|
|||
try {
|
||||
exifInterface = new ExifInterface(inputStream);
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, Log.getStackTraceString(e));
|
||||
IOUtils.closeQuietly(inputStream);
|
||||
return ExifInterface.ORIENTATION_UNDEFINED;
|
||||
}
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
/* Copyright 2017 Andrew Dawson
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.util;
|
||||
|
||||
import com.keylesspalace.tusky.BuildConfig;
|
||||
|
||||
/**A wrapper for android.util.Log that allows for disabling logging, such as for release builds.*/
|
||||
public class Log {
|
||||
private static final boolean LOGGING_ENABLED = BuildConfig.DEBUG;
|
||||
|
||||
public static void i(String tag, String string) {
|
||||
if (LOGGING_ENABLED) {
|
||||
android.util.Log.i(tag, string);
|
||||
}
|
||||
}
|
||||
|
||||
public static void e(String tag, String string) {
|
||||
if (LOGGING_ENABLED) {
|
||||
android.util.Log.e(tag, string);
|
||||
}
|
||||
}
|
||||
|
||||
public static void d(String tag, String string) {
|
||||
if (LOGGING_ENABLED) {
|
||||
android.util.Log.d(tag, string);
|
||||
}
|
||||
}
|
||||
|
||||
public static void v(String tag, String string) {
|
||||
if (LOGGING_ENABLED) {
|
||||
android.util.Log.v(tag, string);
|
||||
}
|
||||
}
|
||||
|
||||
public static void w(String tag, String string) {
|
||||
if (LOGGING_ENABLED) {
|
||||
android.util.Log.w(tag, string);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ import android.provider.Settings;
|
|||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.util.Log;
|
||||
|
||||
import com.keylesspalace.tusky.MainActivity;
|
||||
import com.keylesspalace.tusky.R;
|
||||
|
@ -41,6 +42,9 @@ import org.json.JSONArray;
|
|||
import org.json.JSONException;
|
||||
|
||||
public class NotificationMaker {
|
||||
|
||||
public static final String TAG = "NotificationMaker";
|
||||
|
||||
public static void make(final Context context, final int notifyId, Notification body) {
|
||||
final SharedPreferences preferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
@ -68,7 +72,7 @@ public class NotificationMaker {
|
|||
alreadyContains = true;
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
Log.d(TAG, Log.getStackTraceString(e));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,7 +133,7 @@ public class NotificationMaker {
|
|||
builder.setContentTitle(String.format(context.getString(R.string.notification_title_summary), currentNotifications.length()))
|
||||
.setContentText(truncateWithEllipses(joinNames(context, currentNotifications), 40));
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
Log.d(TAG, Log.getStackTraceString(e));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ package com.keylesspalace.tusky.util;
|
|||
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import com.keylesspalace.tusky.BuildConfig;
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.content.ClipboardManager;
|
|||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.webkit.URLUtil;
|
||||
|
||||
import org.jsoup.Connection;
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
package com.keylesspalace.tusky.util;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.Spanned;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.entity.Notification;
|
||||
import com.keylesspalace.tusky.json.SpannedTypeAdapter;
|
||||
import com.keylesspalace.tusky.json.StringWithEmoji;
|
||||
import com.keylesspalace.tusky.json.StringWithEmojiTypeAdapter;
|
||||
|
||||
import org.eclipse.paho.android.service.MqttAndroidClient;
|
||||
import org.eclipse.paho.client.mqttv3.DisconnectedBufferOptions;
|
||||
import org.eclipse.paho.client.mqttv3.IMqttActionListener;
|
||||
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
|
||||
import org.eclipse.paho.client.mqttv3.IMqttToken;
|
||||
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
|
||||
import org.eclipse.paho.client.mqttv3.MqttClient;
|
||||
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
|
||||
import org.eclipse.paho.client.mqttv3.MqttException;
|
||||
import org.eclipse.paho.client.mqttv3.MqttMessage;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static android.content.Context.NOTIFICATION_SERVICE;
|
||||
|
||||
public class PushNotificationClient {
|
||||
private static final String TAG = "PushNotificationClient";
|
||||
private static final int NOTIFY_ID = 666;
|
||||
|
||||
private static class QueuedAction {
|
||||
enum Type {
|
||||
SUBSCRIBE,
|
||||
UNSUBSCRIBE,
|
||||
DISCONNECT,
|
||||
}
|
||||
|
||||
Type type;
|
||||
String topic;
|
||||
|
||||
QueuedAction(Type type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
QueuedAction(Type type, String topic) {
|
||||
this.type = type;
|
||||
this.topic = topic;
|
||||
}
|
||||
}
|
||||
|
||||
private MqttAndroidClient mqttAndroidClient;
|
||||
private ArrayDeque<QueuedAction> queuedActions;
|
||||
private ArrayList<String> subscribedTopics;
|
||||
|
||||
public PushNotificationClient(final @NonNull Context applicationContext,
|
||||
@NonNull String serverUri) {
|
||||
queuedActions = new ArrayDeque<>();
|
||||
subscribedTopics = new ArrayList<>();
|
||||
|
||||
// Create the MQTT client.
|
||||
String clientId = MqttClient.generateClientId();
|
||||
mqttAndroidClient = new MqttAndroidClient(applicationContext, serverUri, clientId);
|
||||
mqttAndroidClient.setCallback(new MqttCallbackExtended() {
|
||||
@Override
|
||||
public void connectComplete(boolean reconnect, String serverURI) {
|
||||
if (reconnect) {
|
||||
flushQueuedActions();
|
||||
for (String topic : subscribedTopics) {
|
||||
subscribeToTopic(topic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionLost(Throwable cause) {
|
||||
onConnectionLost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageArrived(String topic, MqttMessage message) throws Exception {
|
||||
onMessageReceived(applicationContext, new String(message.getPayload()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliveryComplete(IMqttDeliveryToken token) {
|
||||
// This client is read-only, so this is unused.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void flushQueuedActions() {
|
||||
while (!queuedActions.isEmpty()) {
|
||||
QueuedAction action = queuedActions.pop();
|
||||
switch (action.type) {
|
||||
case SUBSCRIBE: subscribeToTopic(action.topic); break;
|
||||
case UNSUBSCRIBE: unsubscribeToTopic(action.topic); break;
|
||||
case DISCONNECT: disconnect(); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Connect to the MQTT broker. */
|
||||
public void connect(Context context) {
|
||||
MqttConnectOptions options = new MqttConnectOptions();
|
||||
options.setAutomaticReconnect(true);
|
||||
options.setCleanSession(false);
|
||||
try {
|
||||
String password = context.getString(R.string.tusky_api_keystore_password);
|
||||
InputStream keystore = context.getResources().openRawResource(R.raw.keystore_tusky_api);
|
||||
try {
|
||||
options.setSocketFactory(mqttAndroidClient.getSSLSocketFactory(keystore, password));
|
||||
} finally {
|
||||
IOUtils.closeQuietly(keystore);
|
||||
}
|
||||
mqttAndroidClient.connect(options).setActionCallback(new IMqttActionListener() {
|
||||
@Override
|
||||
public void onSuccess(IMqttToken asyncActionToken) {
|
||||
DisconnectedBufferOptions bufferOptions = new DisconnectedBufferOptions();
|
||||
bufferOptions.setBufferEnabled(true);
|
||||
bufferOptions.setBufferSize(100);
|
||||
bufferOptions.setPersistBuffer(false);
|
||||
bufferOptions.setDeleteOldestMessages(false);
|
||||
mqttAndroidClient.setBufferOpts(bufferOptions);
|
||||
onConnectionSuccess();
|
||||
flushQueuedActions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
|
||||
Log.e(TAG, "An exception occurred while connecting. " + exception.getMessage()
|
||||
+ " " + exception.getCause());
|
||||
onConnectionFailure();
|
||||
}
|
||||
});
|
||||
} catch (MqttException e) {
|
||||
Log.e(TAG, "An exception occurred while connecpting. " + e.getMessage());
|
||||
onConnectionFailure();
|
||||
}
|
||||
}
|
||||
|
||||
private void onConnectionSuccess() {
|
||||
Log.v(TAG, "The connection succeeded.");
|
||||
}
|
||||
|
||||
private void onConnectionFailure() {
|
||||
Log.v(TAG, "The connection failed.");
|
||||
}
|
||||
|
||||
private void onConnectionLost() {
|
||||
Log.v(TAG, "The connection was lost.");
|
||||
}
|
||||
|
||||
/** Disconnect from the MQTT broker. */
|
||||
public void disconnect() {
|
||||
if (!mqttAndroidClient.isConnected()) {
|
||||
queuedActions.add(new QueuedAction(QueuedAction.Type.DISCONNECT));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mqttAndroidClient.disconnect();
|
||||
} catch (MqttException ex) {
|
||||
Log.e(TAG, "An exception occurred while disconnecting.");
|
||||
onDisconnectFailed();
|
||||
}
|
||||
}
|
||||
|
||||
private void onDisconnectFailed() {
|
||||
Log.v(TAG, "Failed while disconnecting from the broker.");
|
||||
}
|
||||
|
||||
/** Subscribe to the push notification topic. */
|
||||
public void subscribeToTopic(final String topic) {
|
||||
if (!mqttAndroidClient.isConnected()) {
|
||||
queuedActions.add(new QueuedAction(QueuedAction.Type.SUBSCRIBE, topic));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mqttAndroidClient.subscribe(topic, 0, null, new IMqttActionListener() {
|
||||
@Override
|
||||
public void onSuccess(IMqttToken asyncActionToken) {
|
||||
subscribedTopics.add(topic);
|
||||
onConnectionSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
|
||||
Log.e(TAG, "An exception occurred while subscribing." + exception.getMessage());
|
||||
onConnectionFailure();
|
||||
}
|
||||
});
|
||||
} catch (MqttException e) {
|
||||
Log.e(TAG, "An exception occurred while subscribing." + e.getMessage());
|
||||
onConnectionFailure();
|
||||
}
|
||||
}
|
||||
|
||||
/** Unsubscribe from the push notification topic. */
|
||||
public void unsubscribeToTopic(String topic) {
|
||||
if (!mqttAndroidClient.isConnected()) {
|
||||
queuedActions.add(new QueuedAction(QueuedAction.Type.UNSUBSCRIBE, topic));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mqttAndroidClient.unsubscribe(topic);
|
||||
subscribedTopics.remove(topic);
|
||||
} catch (MqttException e) {
|
||||
Log.e(TAG, "An exception occurred while unsubscribing." + e.getMessage());
|
||||
onConnectionFailure();
|
||||
}
|
||||
}
|
||||
|
||||
private void onMessageReceived(final Context context, String message) {
|
||||
Log.v(TAG, "Notification received: " + message);
|
||||
|
||||
Gson gson = new GsonBuilder()
|
||||
.registerTypeAdapter(Spanned.class, new SpannedTypeAdapter())
|
||||
.registerTypeAdapter(StringWithEmoji.class, new StringWithEmojiTypeAdapter())
|
||||
.create();
|
||||
Notification notification = gson.fromJson(message, Notification.class);
|
||||
|
||||
NotificationMaker.make(context, NOTIFY_ID, notification);
|
||||
}
|
||||
|
||||
public void clearNotifications(Context context) {
|
||||
((NotificationManager) (context.getSystemService(NOTIFICATION_SERVICE))).cancel(NOTIFY_ID);
|
||||
}
|
||||
|
||||
public String getDeviceToken() {
|
||||
return mqttAndroidClient.getClientId();
|
||||
}
|
||||
}
|
|
@ -27,7 +27,6 @@ import com.keylesspalace.tusky.util.Assert;
|
|||
public class FlowLayout extends ViewGroup {
|
||||
private int paddingHorizontal; // internal padding between child views
|
||||
private int paddingVertical; //
|
||||
private int totalHeight;
|
||||
|
||||
public FlowLayout(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
|
@ -35,11 +34,10 @@ public class FlowLayout extends ViewGroup {
|
|||
|
||||
public FlowLayout(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
TypedArray a = context.getTheme().obtainStyledAttributes(
|
||||
attrs, R.styleable.FlowLayout, 0, 0);
|
||||
TypedArray a = context.getTheme()
|
||||
.obtainStyledAttributes(attrs, R.styleable.FlowLayout, 0, 0);
|
||||
try {
|
||||
paddingHorizontal = a.getDimensionPixelSize(
|
||||
R.styleable.FlowLayout_paddingHorizontal, 0);
|
||||
paddingHorizontal = a.getDimensionPixelSize(R.styleable.FlowLayout_paddingHorizontal, 0);
|
||||
paddingVertical = a.getDimensionPixelSize(R.styleable.FlowLayout_paddingVertical, 0);
|
||||
} finally {
|
||||
a.recycle();
|
||||
|
@ -60,26 +58,29 @@ public class FlowLayout extends ViewGroup {
|
|||
} else {
|
||||
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
|
||||
}
|
||||
totalHeight = 0;
|
||||
int rowHeight = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
View child = getChildAt(i);
|
||||
if (child.getVisibility() != GONE) {
|
||||
child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
|
||||
childHeightMeasureSpec);
|
||||
int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST);
|
||||
child.measure(widthSpec, childHeightMeasureSpec);
|
||||
int childwidth = child.getMeasuredWidth();
|
||||
totalHeight = Math.max(totalHeight, child.getMeasuredHeight() + paddingVertical);
|
||||
int childHeight = child.getMeasuredHeight();
|
||||
if (x + childwidth > width) {
|
||||
x = getPaddingLeft();
|
||||
y += totalHeight;
|
||||
y += rowHeight;
|
||||
rowHeight = childHeight + paddingVertical;
|
||||
} else {
|
||||
rowHeight = Math.max(rowHeight, childHeight + paddingVertical);
|
||||
}
|
||||
x += childwidth + paddingHorizontal;
|
||||
}
|
||||
}
|
||||
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) {
|
||||
height = y + totalHeight;
|
||||
height = y + rowHeight;
|
||||
} else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
|
||||
if (y + totalHeight < height) {
|
||||
height = y + totalHeight;
|
||||
if (y + rowHeight < height) {
|
||||
height = y + rowHeight;
|
||||
}
|
||||
}
|
||||
height += 5; // Fudge to avoid clipping bottom of last row.
|
||||
|
@ -91,6 +92,7 @@ public class FlowLayout extends ViewGroup {
|
|||
final int width = r - l;
|
||||
int x = getPaddingLeft();
|
||||
int y = getPaddingTop();
|
||||
int rowHeight = 0;
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
View child = getChildAt(i);
|
||||
if (child.getVisibility() != GONE) {
|
||||
|
@ -98,11 +100,14 @@ public class FlowLayout extends ViewGroup {
|
|||
int childHeight = child.getMeasuredHeight();
|
||||
if (x + childWidth > width) {
|
||||
x = getPaddingLeft();
|
||||
y += totalHeight;
|
||||
y += rowHeight;
|
||||
rowHeight = childHeight + paddingVertical;
|
||||
} else {
|
||||
rowHeight = Math.max(childHeight, rowHeight);
|
||||
}
|
||||
child.layout(x, y, x + childWidth, y + childHeight);
|
||||
x += childWidth + paddingHorizontal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue