diff --git a/app/build.gradle b/app/build.gradle index 553f1feed..a71f3b4dc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -25,6 +25,9 @@ dependencies { androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) + compile('com.mikepenz:materialdrawer:5.8.2@aar') { + transitive = true + } compile 'com.android.support:appcompat-v7:25.2.0' compile 'com.android.support:recyclerview-v7:25.2.0' compile 'com.android.support:support-v13:25.2.0' @@ -32,16 +35,19 @@ dependencies { compile 'com.squareup.picasso:picasso:2.5.2' compile 'com.pkmmte.view:circularimageview:1.1' compile 'com.github.peter9870:sparkbutton:master' - testCompile 'junit:junit:4.12' compile 'com.mikhaellopez:circularfillableloaders:1.2.0' compile 'com.squareup.retrofit2:retrofit:2.2.0' compile 'com.squareup.retrofit2:converter-gson:2.1.0' - compile('com.mikepenz:materialdrawer:5.8.2@aar') { - transitive = true - } compile 'com.github.chrisbanes:PhotoView:1.3.1' compile 'com.mikepenz:google-material-typeface:3.0.1.0.original@aar' compile 'com.github.arimorty:floatingsearchview:2.0.3' compile 'com.jakewharton:butterknife:8.4.0' + compile 'com.google.firebase:firebase-messaging:10.0.1' + compile 'com.google.firebase:firebase-crash:10.0.1' + testCompile 'junit:junit:4.12' annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0' } + + + +apply plugin: 'com.google.gms.google-services' \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4bdc081e5..841938264 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,7 +4,7 @@ - + + + - + + - + @@ -42,10 +47,17 @@ - + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index 14ec4af38..689df0dcd 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -47,12 +47,14 @@ import retrofit2.converter.gson.GsonConverterFactory; * activity extend from it. */ public class BaseActivity extends AppCompatActivity { protected MastodonAPI mastodonAPI; + protected TuskyAPI tuskyAPI; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); createMastodonAPI(); + createTuskyAPI(); if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("lightTheme", false)) { setTheme(R.style.AppTheme_Light); @@ -121,6 +123,14 @@ public class BaseActivity extends AppCompatActivity { mastodonAPI = retrofit.create(MastodonAPI.class); } + protected void createTuskyAPI() { + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(getString(R.string.tusky_api_url)) + .build(); + + tuskyAPI = retrofit.create(TuskyAPI.class); + } + @Override public boolean onCreateOptionsMenu(Menu menu) { TypedValue value = new TypedValue(); diff --git a/app/src/main/java/com/keylesspalace/tusky/BlocksAdapter.java b/app/src/main/java/com/keylesspalace/tusky/BlocksAdapter.java index 52f64a52e..6bd00803c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BlocksAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/BlocksAdapter.java @@ -20,15 +20,20 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; +import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; import com.keylesspalace.tusky.entity.Account; +import com.pkmmte.view.CircularImageView; import com.squareup.picasso.Picasso; import java.util.HashSet; import java.util.Set; +import butterknife.BindView; +import butterknife.ButterKnife; + class BlocksAdapter extends AccountAdapter { private static final int VIEW_TYPE_BLOCKED_USER = 0; private static final int VIEW_TYPE_FOOTER = 1; @@ -88,19 +93,17 @@ class BlocksAdapter extends AccountAdapter { notifyItemChanged(position); } - private static class BlockedUserViewHolder extends RecyclerView.ViewHolder { - private ImageView avatar; - private TextView username; - private TextView displayName; - private Button unblock; + static class BlockedUserViewHolder extends RecyclerView.ViewHolder { + @BindView(R.id.blocked_user_avatar) CircularImageView avatar; + @BindView(R.id.blocked_user_username) TextView username; + @BindView(R.id.blocked_user_display_name) TextView displayName; + @BindView(R.id.blocked_user_unblock) ImageButton unblock; + private String id; BlockedUserViewHolder(View itemView) { super(itemView); - avatar = (ImageView) itemView.findViewById(R.id.blocked_user_avatar); - displayName = (TextView) itemView.findViewById(R.id.blocked_user_display_name); - username = (TextView) itemView.findViewById(R.id.blocked_user_username); - unblock = (Button) itemView.findViewById(R.id.blocked_user_unblock); + ButterKnife.bind(this, itemView); } void setupWithAccount(Account account) { @@ -118,13 +121,6 @@ class BlocksAdapter extends AccountAdapter { void setupActionListener(final AccountActionListener listener, final boolean blocked, final int position) { - int unblockTextId; - if (blocked) { - unblockTextId = R.string.action_unblock; - } else { - unblockTextId = R.string.action_block; - } - unblock.setText(unblock.getContext().getString(unblockTextId)); unblock.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { diff --git a/app/src/main/java/com/keylesspalace/tusky/ConversationLineItemDecoration.java b/app/src/main/java/com/keylesspalace/tusky/ConversationLineItemDecoration.java new file mode 100644 index 000000000..9dbce756b --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/ConversationLineItemDecoration.java @@ -0,0 +1,42 @@ +package com.keylesspalace.tusky; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.support.v7.widget.RecyclerView; +import android.util.TypedValue; +import android.view.View; + +import static android.util.TypedValue.COMPLEX_UNIT_DIP; + +class ConversationLineItemDecoration extends RecyclerView.ItemDecoration { + private final Context mContext; + private final Drawable mDivider; + + public ConversationLineItemDecoration(Context context, Drawable divider) { + mContext = context; + mDivider = divider; + } + + @Override + public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { + // Fun fact: this method draws in pixels, but all layouts are in DP, so I'm using the divider's + // own 2dp width to calculate what I want + int dividerLeft = parent.getPaddingLeft() + mContext.getResources().getDimensionPixelSize(R.dimen.status_left_line_margin); + int dividerRight = dividerLeft + mDivider.getIntrinsicWidth(); + + int childCount = parent.getChildCount(); + int avatarMargin = mContext.getResources().getDimensionPixelSize(R.dimen.account_avatar_margin); + + for (int i = 0; i < childCount; i++) { + View child = parent.getChildAt(i); + + int dividerTop = child.getTop() + (i == 0 ? avatarMargin : 0); + int dividerBottom = (i == childCount - 1 ? child.getTop() + avatarMargin : child.getBottom()); + + mDivider.setBounds(dividerLeft, dividerTop, dividerRight, dividerBottom); + mDivider.draw(c); + } + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/FollowAdapter.java b/app/src/main/java/com/keylesspalace/tusky/FollowAdapter.java index d15708cfd..248fa6b6d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/FollowAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/FollowAdapter.java @@ -24,8 +24,11 @@ import android.widget.ImageView; import android.widget.TextView; import com.keylesspalace.tusky.entity.Account; +import com.pkmmte.view.CircularImageView; import com.squareup.picasso.Picasso; +import butterknife.ButterKnife; + /** Both for follows and following lists. */ class FollowAdapter extends AccountAdapter { private static final int VIEW_TYPE_ACCOUNT = 0; @@ -77,8 +80,7 @@ class FollowAdapter extends AccountAdapter { private View container; private TextView username; private TextView displayName; - private TextView note; - private ImageView avatar; + private CircularImageView avatar; private String id; AccountViewHolder(View itemView) { @@ -86,8 +88,7 @@ class FollowAdapter extends AccountAdapter { container = itemView.findViewById(R.id.account_container); username = (TextView) itemView.findViewById(R.id.account_username); displayName = (TextView) itemView.findViewById(R.id.account_display_name); - note = (TextView) itemView.findViewById(R.id.account_note); - avatar = (ImageView) itemView.findViewById(R.id.account_avatar); + avatar = (CircularImageView) itemView.findViewById(R.id.account_avatar); } void setupWithAccount(Account account) { @@ -96,7 +97,6 @@ class FollowAdapter extends AccountAdapter { String formattedUsername = String.format(format, account.username); username.setText(formattedUsername); displayName.setText(account.getDisplayName()); - note.setText(account.note); Context context = avatar.getContext(); Picasso.with(context) .load(account.avatar) diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java index 226264337..cb1504ef5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java @@ -20,6 +20,7 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.graphics.PorterDuff; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -27,6 +28,7 @@ import android.os.SystemClock; import android.preference.PreferenceManager; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.TabLayout; +import android.support.v4.content.ContextCompat; import android.support.v4.view.ViewPager; import android.os.Bundle; import android.support.v7.widget.Toolbar; @@ -41,6 +43,7 @@ import android.widget.TextView; import com.arlib.floatingsearchview.FloatingSearchView; import com.arlib.floatingsearchview.suggestions.SearchSuggestionsAdapter; import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion; +import com.google.firebase.iid.FirebaseInstanceId; import com.keylesspalace.tusky.entity.Account; import com.mikepenz.google_material_typeface_library.GoogleMaterial; import com.mikepenz.materialdrawer.AccountHeader; @@ -60,6 +63,9 @@ import com.squareup.picasso.Picasso; import java.util.List; import java.util.Stack; +import butterknife.BindView; +import butterknife.ButterKnife; +import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; @@ -67,28 +73,27 @@ import retrofit2.Response; public class MainActivity extends BaseActivity { private static final String TAG = "MainActivity"; // logging tag and Volley request tag - private AlarmManager alarmManager; - private PendingIntent serviceAlarmIntent; - private boolean notificationServiceEnabled; private String loggedInAccountId; private String loggedInAccountUsername; Stack pageHistory = new Stack(); - private ViewPager viewPager; private AccountHeader headerResult; private Drawer drawer; + @BindView(R.id.floating_search_view) FloatingSearchView searchView; + @BindView(R.id.floating_btn) FloatingActionButton floatingBtn; + @BindView(R.id.tab_layout) TabLayout tabLayout; + @BindView(R.id.pager) ViewPager viewPager; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + ButterKnife.bind(this); + // Fetch user info while we're doing other things. fetchUserInfo(); - //Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - //setSupportActionBar(toolbar); - - FloatingActionButton floatingBtn = (FloatingActionButton) findViewById(R.id.floating_btn); floatingBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -97,8 +102,93 @@ public class MainActivity extends BaseActivity { } }); - final FloatingSearchView searchView = (FloatingSearchView) findViewById(R.id.floating_search_view); + setupDrawer(); + setupSearchView(); + // Setup the tabs and timeline pager. + TimelinePagerAdapter adapter = new TimelinePagerAdapter(getSupportFragmentManager()); + String[] pageTitles = { + getString(R.string.title_home), + getString(R.string.title_notifications), + getString(R.string.title_public) + }; + adapter.setPageTitles(pageTitles); + + int pageMargin = getResources().getDimensionPixelSize(R.dimen.tab_page_margin); + viewPager.setPageMargin(pageMargin); + Drawable pageMarginDrawable = ThemeUtils.getDrawable(this, R.attr.tab_page_margin_drawable, + R.drawable.tab_page_margin_dark); + viewPager.setPageMarginDrawable(pageMarginDrawable); + viewPager.setAdapter(adapter); + + tabLayout.setupWithViewPager(viewPager); + + tabLayout.getTabAt(0).setIcon(R.drawable.ic_home_24dp); + tabLayout.getTabAt(1).setIcon(R.drawable.ic_notifications_24dp); + tabLayout.getTabAt(2).setIcon(R.drawable.ic_public_24dp); + + tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { + @Override + public void onTabSelected(TabLayout.Tab tab) { + viewPager.setCurrentItem(tab.getPosition()); + + if (pageHistory.empty()) { + pageHistory.push(0); + } + + if (pageHistory.contains(tab.getPosition())) { + pageHistory.remove(pageHistory.indexOf(tab.getPosition())); + } + + pageHistory.push(tab.getPosition()); + tintTab(tab, true); + } + + @Override + public void onTabUnselected(TabLayout.Tab tab) { + tintTab(tab, false); + } + + @Override + public void onTabReselected(TabLayout.Tab tab) { + + } + }); + + Intent intent = getIntent(); + + if (intent != null) { + int tabPosition = intent.getIntExtra("tab_position", 0); + + if (tabPosition != 0) { + tabLayout.getTabAt(tabPosition).select(); + tintTab(tabLayout.getTabAt(tabPosition), true); + } else { + tintTab(tabLayout.getTabAt(0), true); + } + } else { + tintTab(tabLayout.getTabAt(0), true); + } + + // Setup push notifications + tuskyAPI.register(getBaseUrl(), getAccessToken(), FirebaseInstanceId.getInstance().getToken()).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + + } + + @Override + public void onFailure(Call call, Throwable t) { + + } + }); + } + + private void tintTab(TabLayout.Tab tab, boolean tinted) { + tab.getIcon().setColorFilter(ContextCompat.getColor(this, tinted ? R.color.color_accent_dark : R.color.toolbar_icon_dark), PorterDuff.Mode.SRC_IN); + } + + private void setupDrawer() { headerResult = new AccountHeaderBuilder() .withActivity(this) .withSelectionListEnabledForSingleProfile(false) @@ -152,18 +242,7 @@ public class MainActivity extends BaseActivity { Intent intent = new Intent(MainActivity.this, PreferencesActivity.class); startActivity(intent); } else if (drawerItemIdentifier == 4) { - if (notificationServiceEnabled) { - alarmManager.cancel(serviceAlarmIntent); - } - SharedPreferences preferences = getSharedPreferences( - getString(R.string.preferences_file_key), Context.MODE_PRIVATE); - SharedPreferences.Editor editor = preferences.edit(); - editor.remove("domain"); - editor.remove("accessToken"); - editor.apply(); - Intent intent = new Intent(MainActivity.this, SplashActivity.class); - startActivity(intent); - finish(); + logout(); } } @@ -171,7 +250,33 @@ public class MainActivity extends BaseActivity { } }) .build(); + } + private void logout() { + tuskyAPI.unregister(getBaseUrl(), getAccessToken()).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + + } + + @Override + public void onFailure(Call call, Throwable t) { + + } + }); + + SharedPreferences preferences = getSharedPreferences(getString(R.string.preferences_file_key), Context.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.remove("domain"); + editor.remove("accessToken"); + editor.apply(); + + Intent intent = new Intent(MainActivity.this, LoginActivity.class); + startActivity(intent); + finish(); + } + + private void setupSearchView() { searchView.attachNavigationDrawerToMenuButton(drawer.getDrawerLayout()); searchView.setOnQueryChangeListener(new FloatingSearchView.OnQueryChangeListener() { @@ -237,70 +342,6 @@ public class MainActivity extends BaseActivity { textView.setEllipsize(TextUtils.TruncateAt.END); } }); - - // Setup the tabs and timeline pager. - TimelinePagerAdapter adapter = new TimelinePagerAdapter(getSupportFragmentManager()); - String[] pageTitles = { - getString(R.string.title_home), - getString(R.string.title_notifications), - getString(R.string.title_public) - }; - adapter.setPageTitles(pageTitles); - viewPager = (ViewPager) findViewById(R.id.pager); - int pageMargin = getResources().getDimensionPixelSize(R.dimen.tab_page_margin); - viewPager.setPageMargin(pageMargin); - Drawable pageMarginDrawable = ThemeUtils.getDrawable(this, R.attr.tab_page_margin_drawable, - R.drawable.tab_page_margin_dark); - viewPager.setPageMarginDrawable(pageMarginDrawable); - viewPager.setAdapter(adapter); - - TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout); - tabLayout.setupWithViewPager(viewPager); - - tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { - @Override - public void onTabSelected(TabLayout.Tab tab) { - viewPager.setCurrentItem(tab.getPosition()); - - if (pageHistory.empty()) { - pageHistory.push(0); - } - - if (pageHistory.contains(tab.getPosition())) { - pageHistory.remove(pageHistory.indexOf(tab.getPosition())); - } - - pageHistory.push(tab.getPosition()); - } - - @Override - public void onTabUnselected(TabLayout.Tab tab) { - - } - - @Override - public void onTabReselected(TabLayout.Tab tab) { - - } - }); - - // Retrieve notification update preference. - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); - notificationServiceEnabled = preferences.getBoolean("pullNotifications", true); - String minutesString = preferences.getString("pullNotificationCheckInterval", "15"); - long notificationCheckInterval = 60 * 1000 * Integer.valueOf(minutesString); - // Start up the PullNotificationsService. - alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - Intent intent = new Intent(this, PullNotificationService.class); - final int SERVICE_REQUEST_CODE = 8574603; // This number is arbitrary. - serviceAlarmIntent = PendingIntent.getService(this, SERVICE_REQUEST_CODE, intent, - PendingIntent.FLAG_UPDATE_CURRENT); - if (notificationServiceEnabled) { - alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, - SystemClock.elapsedRealtime(), notificationCheckInterval, serviceAlarmIntent); - } else { - alarmManager.cancel(serviceAlarmIntent); - } } private void fetchUserInfo() { diff --git a/app/src/main/java/com/keylesspalace/tusky/MyFirebaseInstanceIdService.java b/app/src/main/java/com/keylesspalace/tusky/MyFirebaseInstanceIdService.java new file mode 100644 index 000000000..7c1733691 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/MyFirebaseInstanceIdService.java @@ -0,0 +1,61 @@ +package com.keylesspalace.tusky; + +import android.content.Context; +import android.content.SharedPreferences; + +import com.google.firebase.iid.FirebaseInstanceId; +import com.google.firebase.iid.FirebaseInstanceIdService; + +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; + +public class MyFirebaseInstanceIdService extends FirebaseInstanceIdService { + + private TuskyAPI tuskyAPI; + + protected void createTuskyAPI() { + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(getString(R.string.tusky_api_url)) + .build(); + + tuskyAPI = retrofit.create(TuskyAPI.class); + } + + @Override + public void onTokenRefresh() { + createTuskyAPI(); + + String refreshedToken = FirebaseInstanceId.getInstance().getToken(); + SharedPreferences preferences = getSharedPreferences(getString(R.string.preferences_file_key), Context.MODE_PRIVATE); + String accessToken = preferences.getString("accessToken", null); + String domain = preferences.getString("domain", null); + + if (accessToken != null && domain != null) { + tuskyAPI.unregister("https://" + domain, accessToken).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + + } + + @Override + public void onFailure(Call call, Throwable t) { + + } + }); + tuskyAPI.register("https://" + domain, accessToken, refreshedToken).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + + } + + @Override + public void onFailure(Call call, Throwable t) { + + } + }); + } + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/MyFirebaseMessagingService.java b/app/src/main/java/com/keylesspalace/tusky/MyFirebaseMessagingService.java new file mode 100644 index 000000000..31060869e --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/MyFirebaseMessagingService.java @@ -0,0 +1,191 @@ +package com.keylesspalace.tusky; + +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.preference.PreferenceManager; +import android.provider.Settings; +import android.support.v4.app.NotificationCompat; +import android.support.v4.app.TaskStackBuilder; +import android.text.Spanned; + +import com.google.firebase.messaging.FirebaseMessagingService; +import com.google.firebase.messaging.RemoteMessage; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.keylesspalace.tusky.entity.Notification; +import com.squareup.picasso.Picasso; +import com.squareup.picasso.Target; + +import java.io.IOException; + +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +public class MyFirebaseMessagingService extends FirebaseMessagingService { + private MastodonAPI mastodonAPI; + private static final String TAG = "MyFirebaseMessagingService"; + + @Override + public void onMessageReceived(RemoteMessage remoteMessage) { + Log.d(TAG, remoteMessage.getFrom()); + Log.d(TAG, remoteMessage.toString()); + + String notificationId = remoteMessage.getData().get("notification_id"); + + if (notificationId == null) { + Log.e(TAG, "No notification ID in payload!!"); + return; + } + + Log.d(TAG, notificationId); + + createMastodonAPI(); + + mastodonAPI.notification(notificationId).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + buildNotification(response.body()); + } + + @Override + public void onFailure(Call call, Throwable t) { + + } + }); + } + + private void createMastodonAPI() { + SharedPreferences preferences = getSharedPreferences(getString(R.string.preferences_file_key), Context.MODE_PRIVATE); + final String domain = preferences.getString("domain", null); + final String accessToken = preferences.getString("accessToken", null); + + OkHttpClient okHttpClient = new OkHttpClient.Builder() + .addInterceptor(new Interceptor() { + @Override + public okhttp3.Response intercept(Chain chain) throws IOException { + Request originalRequest = chain.request(); + + Request.Builder builder = originalRequest.newBuilder() + .header("Authorization", String.format("Bearer %s", accessToken)); + + Request newRequest = builder.build(); + + return chain.proceed(newRequest); + } + }) + .build(); + + Gson gson = new GsonBuilder() + .registerTypeAdapter(Spanned.class, new SpannedTypeAdapter()) + .create(); + + Retrofit retrofit = new Retrofit.Builder() + .baseUrl("https://" + domain) + .client(okHttpClient) + .addConverterFactory(GsonConverterFactory.create(gson)) + .build(); + + mastodonAPI = retrofit.create(MastodonAPI.class); + } + + private String truncateWithEllipses(String string, int limit) { + if (string.length() < limit) { + return string; + } else { + return string.substring(0, limit - 3) + "..."; + } + } + + private void buildNotification(Notification body) { + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + + Intent resultIntent = new Intent(this, MainActivity.class); + resultIntent.putExtra("tab_position", 1); + TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); + stackBuilder.addParentStack(MainActivity.class); + stackBuilder.addNextIntent(resultIntent); + PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); + + final NotificationCompat.Builder builder = new NotificationCompat.Builder(this) + .setSmallIcon(R.drawable.ic_notify) + .setAutoCancel(true) + .setContentIntent(resultPendingIntent) + .setDefaults(0); // So it doesn't ring twice, notify only in Target callback + + final Integer mId = (int)(System.currentTimeMillis() / 1000); + + Target mTarget = new Target() { + @Override + public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { + builder.setLargeIcon(bitmap); + + if (preferences.getBoolean("notificationAlertSound", true)) { + builder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI); + } + + if (preferences.getBoolean("notificationStyleVibrate", false)) { + builder.setVibrate(new long[] { 500, 500 }); + } + + if (preferences.getBoolean("notificationStyleLight", false)) { + builder.setLights(0xFF00FF8F, 300, 1000); + } + + ((NotificationManager) (getSystemService(NOTIFICATION_SERVICE))).notify(mId, builder.build()); + } + + @Override + public void onBitmapFailed(Drawable errorDrawable) { + + } + + @Override + public void onPrepareLoad(Drawable placeHolderDrawable) { + + } + }; + + Picasso.with(this) + .load(body.account.avatar) + .placeholder(R.drawable.avatar_default) + .into(mTarget); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + builder.setVisibility(android.app.Notification.VISIBILITY_PRIVATE); + builder.setCategory(android.app.Notification.CATEGORY_SOCIAL); + } + + switch (body.type) { + case MENTION: + builder.setContentTitle(String.format(getString(R.string.notification_mention_format), body.account.getDisplayName())) + .setContentText(truncateWithEllipses(body.status.content.toString(), 40)); + break; + case FOLLOW: + builder.setContentTitle(String.format(getString(R.string.notification_follow_format), body.account.getDisplayName())) + .setContentText(truncateWithEllipses(body.account.username, 40)); + break; + case FAVOURITE: + builder.setContentTitle(String.format(getString(R.string.notification_favourite_format), body.account.getDisplayName())) + .setContentText(truncateWithEllipses(body.status.content.toString(), 40)); + break; + case REBLOG: + builder.setContentTitle(String.format(getString(R.string.notification_reblog_format), body.account.getDisplayName())) + .setContentText(truncateWithEllipses(body.status.content.toString(), 40)); + break; + } + + ((NotificationManager) (getSystemService(NOTIFICATION_SERVICE))).notify(mId, builder.build()); + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/NotificationsFragment.java b/app/src/main/java/com/keylesspalace/tusky/NotificationsFragment.java index 7d3f84ede..60798f953 100644 --- a/app/src/main/java/com/keylesspalace/tusky/NotificationsFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/NotificationsFragment.java @@ -17,6 +17,7 @@ package com.keylesspalace.tusky; import android.app.NotificationManager; import android.content.Context; +import android.content.SharedPreferences; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.annotation.Nullable; @@ -58,9 +59,6 @@ public class NotificationsFragment extends SFragment implements @Override public void onCreate(@Nullable Bundle savedInstanceState) { - NotificationManager notificationManager = - (NotificationManager) getActivity().getSystemService(Context.NOTIFICATION_SERVICE); - notificationManager.cancel(PullNotificationService.NOTIFY_ID); super.onCreate(savedInstanceState); } @@ -119,6 +117,12 @@ public class NotificationsFragment extends SFragment implements return rootView; } + @Override + public void onResume() { + super.onResume(); + sendFetchNotificationsRequest(); + } + @Override public void onDestroyView() { TabLayout tabLayout = (TabLayout) getActivity().findViewById(R.id.tab_layout); @@ -165,6 +169,13 @@ public class NotificationsFragment extends SFragment implements if (notifications.size() > 0 && !findNotification(notifications, fromId)) { setFetchTimelineState(FooterViewHolder.State.LOADING); adapter.addItems(notifications); + + // Set last update id for pull notifications so that we don't get notified + // about things we already loaded here + SharedPreferences preferences = getActivity().getSharedPreferences(getString(R.string.preferences_file_key), Context.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putString("lastUpdateId", notifications.get(0).id); + editor.apply(); } else { setFetchTimelineState(FooterViewHolder.State.END_OF_TIMELINE); } diff --git a/app/src/main/java/com/keylesspalace/tusky/PullNotificationService.java b/app/src/main/java/com/keylesspalace/tusky/PullNotificationService.java deleted file mode 100644 index 745e03711..000000000 --- a/app/src/main/java/com/keylesspalace/tusky/PullNotificationService.java +++ /dev/null @@ -1,233 +0,0 @@ -/* Copyright 2017 Andrew Dawson - * - * This file is part of Tusky. - * - * Tusky 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 - * . */ - -package com.keylesspalace.tusky; - -import android.app.*; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.preference.PreferenceManager; -import android.provider.Settings; -import android.support.annotation.Nullable; -import android.support.v4.app.NotificationCompat; -import android.support.v4.app.TaskStackBuilder; -import android.text.Spanned; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.keylesspalace.tusky.entity.*; -import com.keylesspalace.tusky.entity.Notification; -import com.squareup.picasso.Picasso; -import com.squareup.picasso.Target; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import okhttp3.Interceptor; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; - -public class PullNotificationService extends IntentService { - static final int NOTIFY_ID = 6; // This is an arbitrary number. - private static final String TAG = "PullNotifications"; // logging tag - - public PullNotificationService() { - super("Tusky Pull Notification Service"); - } - - @Override - protected void onHandleIntent(Intent intent) { - SharedPreferences preferences = getSharedPreferences( - getString(R.string.preferences_file_key), Context.MODE_PRIVATE); - String domain = preferences.getString("domain", null); - String accessToken = preferences.getString("accessToken", null); - String lastUpdateId = preferences.getString("lastUpdateId", null); - checkNotifications(domain, accessToken, lastUpdateId); - } - - private void checkNotifications(final String domain, final String accessToken, - final String lastUpdateId) { - OkHttpClient okHttpClient = new OkHttpClient.Builder() - .addInterceptor(new Interceptor() { - @Override - public okhttp3.Response intercept(Chain chain) throws IOException { - Request originalRequest = chain.request(); - - Request.Builder builder = originalRequest.newBuilder() - .header("Authorization", String.format("Bearer %s", accessToken)); - - Request newRequest = builder.build(); - - return chain.proceed(newRequest); - } - }) - .build(); - - Gson gson = new GsonBuilder() - .registerTypeAdapter(Spanned.class, new SpannedTypeAdapter()) - .create(); - - Retrofit retrofit = new Retrofit.Builder() - .baseUrl("https://" + domain) - .client(okHttpClient) - .addConverterFactory(GsonConverterFactory.create(gson)) - .build(); - - MastodonAPI api = retrofit.create(MastodonAPI.class); - - api.notifications(null, lastUpdateId, null).enqueue(new Callback>() { - @Override - public void onResponse(Call> call, retrofit2.Response> response) { - onCheckNotificationsSuccess(response.body(), lastUpdateId); - } - - @Override - public void onFailure(Call> call, Throwable t) { - onCheckNotificationsFailure((Exception) t); - } - }); - } - - private void onCheckNotificationsSuccess(List notifications, String lastUpdateId) { - List mentions = new ArrayList<>(); - - for (com.keylesspalace.tusky.entity.Notification notification : notifications) { - if (notification.type == com.keylesspalace.tusky.entity.Notification.Type.MENTION) { - Status status = notification.status; - - if (status != null) { - MentionResult mention = new MentionResult(); - mention.content = status.content.toString(); - mention.displayName = notification.account.getDisplayName(); - mention.avatarUrl = status.account.avatar; - mentions.add(mention); - } - } - } - - if (notifications.size() > 0) { - SharedPreferences preferences = getSharedPreferences( - getString(R.string.preferences_file_key), Context.MODE_PRIVATE); - SharedPreferences.Editor editor = preferences.edit(); - editor.putString("lastUpdateId", notifications.get(0).id); - editor.apply(); - } - - if (mentions.size() > 0) { - loadAvatar(mentions, mentions.get(0).avatarUrl); - } - } - - private void onCheckNotificationsFailure(Exception exception) { - Log.e(TAG, "Failed to check notifications. " + exception.getMessage()); - } - - private static class MentionResult { - String displayName; - String content; - String avatarUrl; - } - - private String truncateWithEllipses(String string, int limit) { - if (string.length() < limit) { - return string; - } else { - return string.substring(0, limit - 3) + "..."; - } - } - - private void loadAvatar(final List mentions, String url) { - if (url != null) { - Target target = new Target() { - @Override - public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { - updateNotification(mentions, bitmap); - } - - @Override - public void onBitmapFailed(Drawable errorDrawable) { - updateNotification(mentions, null); - } - - @Override - public void onPrepareLoad(Drawable placeHolderDrawable) {} - }; - Picasso.with(this) - .load(url) - .into(target); - } else { - updateNotification(mentions, null); - } - } - - private void updateNotification(List mentions, @Nullable Bitmap icon) { - final int NOTIFICATION_CONTENT_LIMIT = 40; - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); - String title; - if (mentions.size() > 1) { - title = String.format( - getString(R.string.notification_service_several_mentions), - mentions.size()); - } else { - title = String.format( - getString(R.string.notification_service_one_mention), - mentions.get(0).displayName); - } - NotificationCompat.Builder builder = new NotificationCompat.Builder(this) - .setSmallIcon(R.drawable.ic_notify) - .setContentTitle(title); - if (icon != null) { - builder.setLargeIcon(icon); - } - if (preferences.getBoolean("notificationAlertSound", true)) { - builder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI); - } - if (preferences.getBoolean("notificationStyleVibrate", false)) { - builder.setVibrate(new long[] { 500, 500 }); - } - if (preferences.getBoolean("notificationStyleLight", false)) { - builder.setLights(0xFF00FF8F, 300, 1000); - } - for (int i = 0; i < mentions.size(); i++) { - MentionResult mention = mentions.get(i); - String text = truncateWithEllipses(mention.content, NOTIFICATION_CONTENT_LIMIT); - builder.setContentText(text) - .setNumber(i); - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - builder.setVisibility(android.app.Notification.VISIBILITY_PRIVATE); - builder.setCategory(android.app.Notification.CATEGORY_SOCIAL); - } - Intent resultIntent = new Intent(this, SplashActivity.class); - TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); - stackBuilder.addParentStack(SplashActivity.class); - stackBuilder.addNextIntent(resultIntent); - PendingIntent resultPendingIntent = - stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); - builder.setContentIntent(resultPendingIntent); - NotificationManager notificationManager = - (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - notificationManager.notify(NOTIFY_ID, builder.build()); - } -} diff --git a/app/src/main/java/com/keylesspalace/tusky/SFragment.java b/app/src/main/java/com/keylesspalace/tusky/SFragment.java index d1de088ed..862e598cf 100644 --- a/app/src/main/java/com/keylesspalace/tusky/SFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/SFragment.java @@ -206,7 +206,6 @@ public class SFragment extends Fragment { FragmentManager manager = getFragmentManager(); manager.beginTransaction() - .setCustomAnimations(R.anim.zoom_in, R.anim.zoom_out, R.anim.zoom_in, R.anim.zoom_out) .add(R.id.overlay_fragment_container, newFragment) .addToBackStack(null) .commit(); diff --git a/app/src/main/java/com/keylesspalace/tusky/SplashActivity.java b/app/src/main/java/com/keylesspalace/tusky/SplashActivity.java index d35a3ce17..dcb35fdd2 100644 --- a/app/src/main/java/com/keylesspalace/tusky/SplashActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/SplashActivity.java @@ -22,6 +22,8 @@ import android.content.SharedPreferences; import android.os.Bundle; import android.os.Handler; import android.support.v7.app.AppCompatActivity; +import android.view.Window; +import android.view.WindowManager; public class SplashActivity extends Activity { private static int SPLASH_TIME_OUT = 2000; @@ -29,7 +31,12 @@ public class SplashActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + requestWindowFeature(Window.FEATURE_NO_TITLE); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + setContentView(R.layout.activity_splash); + /* Determine whether the user is currently logged in, and if so go ahead and load the * timeline. Otherwise, start the activity_login screen. */ SharedPreferences preferences = getSharedPreferences( diff --git a/app/src/main/java/com/keylesspalace/tusky/TimelineFragment.java b/app/src/main/java/com/keylesspalace/tusky/TimelineFragment.java index b8c0bfbc2..76579307a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/TimelineFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/TimelineFragment.java @@ -138,6 +138,12 @@ public class TimelineFragment extends SFragment implements return rootView; } + @Override + public void onResume() { + super.onResume(); + sendFetchTimelineRequest(); + } + @Override public void onDestroyView() { if (jumpToTopAllowed()) { diff --git a/app/src/main/java/com/keylesspalace/tusky/TimelinePagerAdapter.java b/app/src/main/java/com/keylesspalace/tusky/TimelinePagerAdapter.java index 7cf0642d1..883d56ea9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/TimelinePagerAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/TimelinePagerAdapter.java @@ -55,6 +55,6 @@ class TimelinePagerAdapter extends FragmentPagerAdapter { @Override public CharSequence getPageTitle(int position) { - return pageTitles[position]; + return null; } } diff --git a/app/src/main/java/com/keylesspalace/tusky/TuskyAPI.java b/app/src/main/java/com/keylesspalace/tusky/TuskyAPI.java new file mode 100644 index 000000000..2df62bc15 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/TuskyAPI.java @@ -0,0 +1,16 @@ +package com.keylesspalace.tusky; + +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.http.Field; +import retrofit2.http.FormUrlEncoded; +import retrofit2.http.POST; + +public interface TuskyAPI { + @FormUrlEncoded + @POST("/register") + Call register(@Field("instance_url") String instanceUrl, @Field("access_token") String accessToken, @Field("device_token") String deviceToken); + @FormUrlEncoded + @POST("/unregister") + Call unregister(@Field("instance_url") String instanceUrl, @Field("access_token") String accessToken); +} diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java b/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java index 39dcef2fc..84354d212 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java @@ -21,20 +21,30 @@ import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.support.v7.app.ActionBar; import android.support.v7.widget.Toolbar; +import android.view.MenuItem; + +import butterknife.BindView; +import butterknife.ButterKnife; public class ViewTagActivity extends BaseActivity { + @BindView(R.id.toolbar) Toolbar toolbar; + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_view_tag); + ButterKnife.bind(this); + String hashtag = getIntent().getStringExtra("hashtag"); - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); ActionBar bar = getSupportActionBar(); + if (bar != null) { bar.setTitle(String.format(getString(R.string.title_tag), hashtag)); + bar.setDisplayHomeAsUpEnabled(true); + bar.setDisplayShowHomeEnabled(true); } FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); @@ -42,4 +52,15 @@ public class ViewTagActivity extends BaseActivity { fragmentTransaction.add(R.id.fragment_container, fragment); fragmentTransaction.commit(); } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: { + onBackPressed(); + return true; + } + } + return super.onOptionsItemSelected(item); + } } diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewThreadFragment.java b/app/src/main/java/com/keylesspalace/tusky/ViewThreadFragment.java index a83c35e2d..de535035b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewThreadFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/ViewThreadFragment.java @@ -20,6 +20,7 @@ import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.design.widget.Snackbar; +import android.support.v4.content.ContextCompat; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; @@ -63,6 +64,7 @@ public class ViewThreadFragment extends SFragment implements StatusActionListene R.drawable.status_divider_dark); divider.setDrawable(drawable); recyclerView.addItemDecoration(divider); + recyclerView.addItemDecoration(new ConversationLineItemDecoration(context, ContextCompat.getDrawable(context, R.drawable.conversation_divider_dark))); adapter = new ThreadAdapter(this); recyclerView.setAdapter(adapter); diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewVideoActivity.java b/app/src/main/java/com/keylesspalace/tusky/ViewVideoActivity.java index 4fd83070a..1ec2fa71e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewVideoActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ViewVideoActivity.java @@ -16,21 +16,53 @@ package com.keylesspalace.tusky; import android.os.Bundle; +import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.MenuItem; import android.widget.MediaController; import android.widget.VideoView; -public class ViewVideoActivity extends AppCompatActivity { +import butterknife.BindView; +import butterknife.ButterKnife; + +public class ViewVideoActivity extends BaseActivity { + @BindView(R.id.video_player) VideoView videoView; + @BindView(R.id.toolbar) Toolbar toolbar; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_view_video); + ButterKnife.bind(this); + + setSupportActionBar(toolbar); + + ActionBar bar = getSupportActionBar(); + + if (bar != null) { + bar.setTitle(null); + bar.setDisplayHomeAsUpEnabled(true); + bar.setDisplayShowHomeEnabled(true); + } + String url = getIntent().getStringExtra("url"); - VideoView videoView = (VideoView) findViewById(R.id.video_player); + videoView.setVideoPath(url); MediaController controller = new MediaController(this); videoView.setMediaController(controller); controller.show(); videoView.start(); } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: { + onBackPressed(); + return true; + } + } + return super.onOptionsItemSelected(item); + } } diff --git a/app/src/main/res/drawable/conversation_divider_dark.xml b/app/src/main/res/drawable/conversation_divider_dark.xml new file mode 100644 index 000000000..42034487b --- /dev/null +++ b/app/src/main/res/drawable/conversation_divider_dark.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_clear_24dp.xml b/app/src/main/res/drawable/ic_clear_24dp.xml new file mode 100644 index 000000000..72af13013 --- /dev/null +++ b/app/src/main/res/drawable/ic_clear_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_home_24dp.xml b/app/src/main/res/drawable/ic_home_24dp.xml new file mode 100644 index 000000000..55e6239b9 --- /dev/null +++ b/app/src/main/res/drawable/ic_home_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_notifications_24dp.xml b/app/src/main/res/drawable/ic_notifications_24dp.xml new file mode 100644 index 000000000..86ed94867 --- /dev/null +++ b/app/src/main/res/drawable/ic_notifications_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_public_24dp.xml b/app/src/main/res/drawable/ic_public_24dp.xml index d976b4244..f66427b10 100644 --- a/app/src/main/res/drawable/ic_public_24dp.xml +++ b/app/src/main/res/drawable/ic_public_24dp.xml @@ -4,6 +4,6 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> diff --git a/app/src/main/res/layout/activity_view_thread.xml b/app/src/main/res/layout/activity_view_thread.xml index d808ea49a..e714bf438 100644 --- a/app/src/main/res/layout/activity_view_thread.xml +++ b/app/src/main/res/layout/activity_view_thread.xml @@ -25,14 +25,12 @@ android:id="@+id/fragment_container" android:layout_width="match_parent" android:layout_height="match_parent" /> - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_view_video.xml b/app/src/main/res/layout/activity_view_video.xml index 77001cba7..38e3cfabd 100644 --- a/app/src/main/res/layout/activity_view_video.xml +++ b/app/src/main/res/layout/activity_view_video.xml @@ -1,14 +1,24 @@ - + android:background="@color/view_video_background" + tools:context=".ViewVideoActivity"> - + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_view_media.xml b/app/src/main/res/layout/fragment_view_media.xml index 1126dbe02..d3b84def2 100644 --- a/app/src/main/res/layout/fragment_view_media.xml +++ b/app/src/main/res/layout/fragment_view_media.xml @@ -2,6 +2,7 @@ - - + - + - - android:layout_centerVertical="true" - android:layout_toRightOf="@id/account_avatar"> + - + - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/item_blocked_user.xml b/app/src/main/res/layout/item_blocked_user.xml index 1d2a4a44d..b1d2b4d5b 100644 --- a/app/src/main/res/layout/item_blocked_user.xml +++ b/app/src/main/res/layout/item_blocked_user.xml @@ -1,60 +1,57 @@ + android:layout_height="72dp" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:gravity="center_vertical"> - + android:layout_alignParentLeft="true" + android:layout_marginRight="24dp" + android:layout_centerVertical="true"/> -