From 642e2af23e444a802e953f92ffeaa3624c3e4bdc Mon Sep 17 00:00:00 2001 From: "serage.betelmal" Date: Sat, 15 Apr 2017 19:05:25 +0100 Subject: [PATCH 01/53] Adding an about activity to the App --- app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 40 +++++++++----- .../keylesspalace/tusky/AboutActivity.java | 25 +++++++++ .../com/keylesspalace/tusky/MainActivity.java | 6 ++- app/src/main/res/layout/activity_about.xml | 54 +++++++++++++++++++ app/src/main/res/values/strings.xml | 3 ++ app/src/main/res/values/styles.xml | 43 ++++++++++----- 7 files changed, 147 insertions(+), 25 deletions(-) create mode 100644 app/src/main/java/com/keylesspalace/tusky/AboutActivity.java create mode 100644 app/src/main/res/layout/activity_about.xml diff --git a/app/build.gradle b/app/build.gradle index ebacc223..1e3de3e1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,6 +50,7 @@ dependencies { 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' + compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha7' testCompile 'junit:junit:4.12' annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 62955811..7bbe6485 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,7 +13,10 @@ android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> - + + @@ -41,55 +44,68 @@ android:windowSoftInputMode="stateVisible|adjustResize"> + + + + + + - + + - + - + - - + - + + android:permission="android.permission.BIND_QUICK_SETTINGS_TILE" + tools:targetApi="24"> - + - + + \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/AboutActivity.java b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.java new file mode 100644 index 00000000..bbbf02cf --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.java @@ -0,0 +1,25 @@ +package com.keylesspalace.tusky; + +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.widget.TextView; + +public class AboutActivity extends AppCompatActivity { + private TextView mVersionTextView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_about); + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + mVersionTextView = (TextView) findViewById(R.id.textView); + String versionName = BuildConfig.VERSION_NAME; + + mVersionTextView. + setText(getString(R.string.about_application_version)+ versionName); + } + +} diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java index 43415269..effc48d0 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java @@ -266,7 +266,8 @@ public class MainActivity extends BaseActivity { new PrimaryDrawerItem().withIdentifier(2).withName(getString(R.string.action_view_blocks)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_block), new DividerDrawerItem(), new SecondaryDrawerItem().withIdentifier(3).withName(getString(R.string.action_view_preferences)).withSelectable(false), - new SecondaryDrawerItem().withIdentifier(4).withName(getString(R.string.action_logout)).withSelectable(false) + new SecondaryDrawerItem().withIdentifier(4).withName(getString(R.string.about_title_activity)).withSelectable(false), + new SecondaryDrawerItem().withIdentifier(5).withName(getString(R.string.action_logout)).withSelectable(false) ) .withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() { @Override @@ -290,6 +291,9 @@ public class MainActivity extends BaseActivity { Intent intent = new Intent(MainActivity.this, PreferencesActivity.class); startActivity(intent); } else if (drawerItemIdentifier == 4) { + Intent intent = new Intent(MainActivity.this, AboutActivity.class); + startActivity(intent); + } else if (drawerItemIdentifier == 5) { logout(); } } diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml new file mode 100644 index 00000000..e395a86a --- /dev/null +++ b/app/src/main/res/layout/activity_about.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7d053474..0ef915e5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -141,4 +141,7 @@ Locked Account + About + App version: + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index a6454085..e0a59373 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -30,7 +30,8 @@ @color/text_color_secondary_dark @color/text_color_tertiary_dark @color/text_color_primary_inverse_dark - @color/text_color_secondary_inverse_dark + @color/text_color_secondary_inverse_dark + @color/text_color_tertiary_inverse_dark @color/text_color_primary_dark @@ -53,11 +54,15 @@ @drawable/tab_page_margin_dark @color/account_header_background_dark @color/toolbar_icon_dark - @color/account_toolbar_icon_collapsed_dark - @style/AppTheme.Account.ToolbarPopupTheme.Dark + + @color/account_toolbar_icon_collapsed_dark + + @style/AppTheme.Account.ToolbarPopupTheme.Dark + @color/toolbar_icon_dark @color/compose_media_button_dark - @color/compose_media_button_disabled_dark + @color/compose_media_button_disabled_dark + @color/color_accent_dark @drawable/border_background_dark @color/image_button_dark @@ -116,8 +121,10 @@ @color/text_color_secondary_light @color/text_color_tertiary_light @color/text_color_primary_inverse_light - @color/text_color_secondary_inverse_light - @color/text_color_tertiary_inverse_light + @color/text_color_secondary_inverse_light + + @color/text_color_tertiary_inverse_light + @color/text_color_primary_light @style/AppTheme.BottomSheetDialog.Light @@ -132,18 +139,25 @@ @drawable/favourite_active_light @drawable/favourite_inactive_light @drawable/toggle_small_light - @color/sensitive_media_warning_background_light + + @color/sensitive_media_warning_background_light + @drawable/media_preview_unloaded_light @drawable/status_divider_light @color/color_accent_light @drawable/tab_page_margin_light @color/account_header_background_light - @color/toolbar_icon_dark - @color/account_toolbar_icon_collapsed_light - @style/AppTheme.Account.ToolbarPopupTheme.Light + @color/toolbar_icon_dark + + + @color/account_toolbar_icon_collapsed_light + + @style/AppTheme.Account.ToolbarPopupTheme.Light + @color/toolbar_icon_light @color/compose_media_button_light - @color/compose_media_button_disabled_light + @color/compose_media_button_disabled_light + @color/compose_mention_light @drawable/border_background_light @color/image_button_light @@ -159,7 +173,8 @@ @color/color_primary_dark_light @color/color_primary_light @color/text_color_primary_light - @color/text_color_primary_dark + @color/text_color_primary_dark + @color/toolbar_background_light @color/text_color_secondary_light @@ -187,4 +202,8 @@ true + - + + From 32c2231406d2923d1690210d7715dc0f999588b5 Mon Sep 17 00:00:00 2001 From: torrentcome Date: Wed, 10 May 2017 15:24:03 +0200 Subject: [PATCH 36/53] (icon) add icon download --- .../main/res/drawable/ic_file_download_black_24dp.xml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 app/src/main/res/drawable/ic_file_download_black_24dp.xml diff --git a/app/src/main/res/drawable/ic_file_download_black_24dp.xml b/app/src/main/res/drawable/ic_file_download_black_24dp.xml new file mode 100644 index 00000000..f5f7221a --- /dev/null +++ b/app/src/main/res/drawable/ic_file_download_black_24dp.xml @@ -0,0 +1,9 @@ + + + From 2e6531b6f9bd0e10a9e9b70cd726093ed00b58ca Mon Sep 17 00:00:00 2001 From: torrentcome Date: Wed, 10 May 2017 15:24:45 +0200 Subject: [PATCH 37/53] (media) add toolbar and download button --- .../tusky/fragment/ViewMediaFragment.java | 55 +++++++++++-------- .../main/res/layout/fragment_view_media.xml | 33 +++++++---- app/src/main/res/menu/view_media_tooblar.xml | 9 +++ 3 files changed, 63 insertions(+), 34 deletions(-) create mode 100644 app/src/main/res/menu/view_media_tooblar.xml diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.java index aa8d41b9..f53a4ef6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.java @@ -15,10 +15,8 @@ package com.keylesspalace.tusky.fragment; -import android.app.AlertDialog; import android.app.DownloadManager; import android.content.Context; -import android.content.DialogInterface; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; @@ -30,18 +28,20 @@ import android.support.annotation.StringRes; import android.support.design.widget.Snackbar; import android.support.v4.app.DialogFragment; import android.support.v4.content.ContextCompat; +import android.support.v7.widget.Toolbar; import android.view.LayoutInflater; +import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.ImageView; -import com.keylesspalace.tusky.R; import com.github.chrisbanes.photoview.OnOutsidePhotoTapListener; import com.github.chrisbanes.photoview.OnSingleFlingListener; import com.github.chrisbanes.photoview.PhotoView; import com.github.chrisbanes.photoview.PhotoViewAttacher; +import com.keylesspalace.tusky.R; import com.squareup.picasso.Callback; import com.squareup.picasso.Picasso; @@ -50,7 +50,7 @@ import java.io.File; import butterknife.BindView; import butterknife.ButterKnife; -public class ViewMediaFragment extends DialogFragment { +public class ViewMediaFragment extends DialogFragment implements Toolbar.OnMenuItemClickListener { private PhotoViewAttacher attacher; @@ -59,6 +59,9 @@ public class ViewMediaFragment extends DialogFragment { @BindView(R.id.view_media_image) PhotoView photoView; + @BindView(R.id.toolbar) + Toolbar toolbar; + public static ViewMediaFragment newInstance(String url) { Bundle arguments = new Bundle(); ViewMediaFragment fragment = new ViewMediaFragment(); @@ -115,22 +118,10 @@ public class ViewMediaFragment extends DialogFragment { } }); - attacher.setOnLongClickListener(new View.OnLongClickListener() { + toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override - public boolean onLongClick(View v) { - - AlertDialog downloadDialog = new AlertDialog.Builder(getContext()).create(); - - downloadDialog.setButton(AlertDialog.BUTTON_NEUTRAL, getString(R.string.dialog_download_image), - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - - downloadImage(); - } - }); - downloadDialog.show(); - return false; + public void onClick(View v) { + dismiss(); } }); @@ -140,6 +131,9 @@ public class ViewMediaFragment extends DialogFragment { @Override public void onSuccess() { rootView.findViewById(R.id.view_media_progress).setVisibility(View.GONE); + toolbar.setOnMenuItemClickListener(ViewMediaFragment.this); + toolbar.inflateMenu(R.menu.view_media_tooblar); + attacher.update(); } @@ -204,9 +198,24 @@ public class ViewMediaFragment extends DialogFragment { private void doErrorDialog(@StringRes int descriptionId, @StringRes int actionId, View.OnClickListener listener) { - Snackbar bar = Snackbar.make(getView(), getString(descriptionId), - Snackbar.LENGTH_SHORT); - bar.setAction(actionId, listener); - bar.show(); + if(getView() != null) { + Snackbar bar = Snackbar.make(getView(), getString(descriptionId), + Snackbar.LENGTH_SHORT); + bar.setAction(actionId, listener); + bar.show(); + } + } + + @Override + public boolean onMenuItemClick(MenuItem item) { + int id = item.getItemId(); + switch (id) { + case R.id.action_download: + downloadImage(); + break; + default: + break; + } + return true; } } diff --git a/app/src/main/res/layout/fragment_view_media.xml b/app/src/main/res/layout/fragment_view_media.xml index 0b97166c..db7833b1 100644 --- a/app/src/main/res/layout/fragment_view_media.xml +++ b/app/src/main/res/layout/fragment_view_media.xml @@ -1,20 +1,31 @@ - - - + android:background="@android:color/black" + android:clickable="true"> + android:layout_height="match_parent" + android:layout_below="@+id/toolbar" /> + + + + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/menu/view_media_tooblar.xml b/app/src/main/res/menu/view_media_tooblar.xml new file mode 100644 index 00000000..1de914f5 --- /dev/null +++ b/app/src/main/res/menu/view_media_tooblar.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file From e24e91b455ef8a4a91d3fa285f3ac5587921e038 Mon Sep 17 00:00:00 2001 From: Pangoraw Date: Thu, 11 May 2017 09:11:15 +0000 Subject: [PATCH 38/53] Allow broadcasting event across activities. --- .../keylesspalace/tusky/AccountActivity.java | 10 +++++ .../tusky/fragment/TimelineFragment.java | 6 +++ .../tusky/util/TimelineReceiver.java | 41 +++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 app/src/main/java/com/keylesspalace/tusky/util/TimelineReceiver.java diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java index 6711781b..ae6f99ac 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java @@ -32,6 +32,7 @@ import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.design.widget.TabLayout; import android.support.v4.app.Fragment; +import android.support.v4.content.LocalBroadcastManager; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPager; import android.support.v7.app.ActionBar; @@ -51,6 +52,7 @@ import com.keylesspalace.tusky.pager.AccountPagerAdapter; import com.keylesspalace.tusky.util.LinkHelper; import com.keylesspalace.tusky.util.Assert; import com.keylesspalace.tusky.util.Log; +import com.keylesspalace.tusky.util.TimelineReceiver; import com.keylesspalace.tusky.util.ThemeUtils; import com.pkmmte.view.CircularImageView; import com.squareup.picasso.Picasso; @@ -467,6 +469,7 @@ public class AccountActivity extends BaseActivity implements SFragment.OnUserRem Snackbar.LENGTH_LONG).show(); } else { followState = FollowState.NOT_FOLLOWING; + broadcast(TimelineReceiver.Types.UNFOLLOW_ACCOUNT, id); } updateButtons(); } else { @@ -517,6 +520,7 @@ public class AccountActivity extends BaseActivity implements SFragment.OnUserRem @Override public void onResponse(Call call, Response response) { if (response.isSuccessful()) { + broadcast(TimelineReceiver.Types.BLOCK_ACCOUNT, id); blocking = response.body().blocking; updateButtons(); } else { @@ -554,6 +558,7 @@ public class AccountActivity extends BaseActivity implements SFragment.OnUserRem @Override public void onResponse(Call call, Response response) { if (response.isSuccessful()) { + broadcast(TimelineReceiver.Types.MUTE_ACCOUNT, id); muting = response.body().muting; updateButtons(); } else { @@ -586,6 +591,11 @@ public class AccountActivity extends BaseActivity implements SFragment.OnUserRem .show(); } + private void broadcast(String action, String id) { + Intent intent = new Intent(action); + intent.putExtra("id", id); + LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent); + } @Override public boolean onOptionsItemSelected(MenuItem item) { diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java index 82205996..dd35d16b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java @@ -23,6 +23,7 @@ import android.preference.PreferenceManager; import android.support.annotation.Nullable; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.TabLayout; +import android.support.v4.content.LocalBroadcastManager; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; @@ -39,6 +40,7 @@ import com.keylesspalace.tusky.interfaces.StatusActionListener; import com.keylesspalace.tusky.interfaces.StatusRemoveListener; import com.keylesspalace.tusky.util.EndlessOnScrollListener; import com.keylesspalace.tusky.util.Log; +import com.keylesspalace.tusky.util.TimelineReceiver; import com.keylesspalace.tusky.util.ThemeUtils; import java.util.List; @@ -72,6 +74,7 @@ public class TimelineFragment extends SFragment implements private EndlessOnScrollListener scrollListener; private TabLayout.OnTabSelectedListener onTabSelectedListener; private boolean hideFab; + private TimelineReceiver timelineReceiver; public static TimelineFragment newInstance(Kind kind) { TimelineFragment fragment = new TimelineFragment(); @@ -120,6 +123,8 @@ public class TimelineFragment extends SFragment implements adapter = new TimelineAdapter(this); recyclerView.setAdapter(adapter); + timelineReceiver = new TimelineReceiver(adapter); + LocalBroadcastManager.getInstance(context.getApplicationContext()).registerReceiver(timelineReceiver, TimelineReceiver.getFilter(kind)); return rootView; } @@ -204,6 +209,7 @@ public class TimelineFragment extends SFragment implements TabLayout tabLayout = (TabLayout) getActivity().findViewById(R.id.tab_layout); tabLayout.removeOnTabSelectedListener(onTabSelectedListener); } + LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(timelineReceiver); super.onDestroyView(); } diff --git a/app/src/main/java/com/keylesspalace/tusky/util/TimelineReceiver.java b/app/src/main/java/com/keylesspalace/tusky/util/TimelineReceiver.java new file mode 100644 index 00000000..f6acc0d9 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/util/TimelineReceiver.java @@ -0,0 +1,41 @@ +package com.keylesspalace.tusky.util; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import com.keylesspalace.tusky.adapter.TimelineAdapter; +import com.keylesspalace.tusky.fragment.TimelineFragment; + +public class TimelineReceiver extends BroadcastReceiver { + public static final class Types { + public static final String UNFOLLOW_ACCOUNT = "UNFOLLOW_ACCOUNT"; + public static final String BLOCK_ACCOUNT = "BLOCK_ACCOUNT"; + public static final String MUTE_ACCOUNT = "MUTE_ACCOUNT"; + } + + TimelineAdapter adapter; + + public TimelineReceiver(TimelineAdapter adapter) { + super(); + this.adapter = adapter; + } + + @Override + public void onReceive(Context context, final Intent intent) { + String id = intent.getStringExtra("id"); + adapter.removeAllByAccountId(id); + } + + public static IntentFilter getFilter(TimelineFragment.Kind kind) { + IntentFilter intentFilter = new IntentFilter(); + if (kind == TimelineFragment.Kind.HOME) { + intentFilter.addAction(Types.UNFOLLOW_ACCOUNT); + } + intentFilter.addAction(Types.BLOCK_ACCOUNT); + intentFilter.addAction(Types.MUTE_ACCOUNT); + + return intentFilter; + } +} From d263d77d7846e18a34bc72a329ef18b3a867cc60 Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Thu, 11 May 2017 18:29:16 +0200 Subject: [PATCH 39/53] sharing of own toots now possible --- app/src/main/res/menu/status_more_for_user.xml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/menu/status_more_for_user.xml b/app/src/main/res/menu/status_more_for_user.xml index 274ce367..28c019e8 100644 --- a/app/src/main/res/menu/status_more_for_user.xml +++ b/app/src/main/res/menu/status_more_for_user.xml @@ -2,7 +2,16 @@ + android:title="@string/action_share"> + + + + + \ No newline at end of file From 8472b1d44f8258f0573f52efb41251405866af90 Mon Sep 17 00:00:00 2001 From: Vavassor Date: Thu, 11 May 2017 16:13:49 -0400 Subject: [PATCH 40/53] Adds store badges to README.md. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ab51d89..0b79a724 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ Tusky is a beautiful Android client for [Mastodon](https://github.com/tootsuite/mastodon). Mastodon is a GNU social-compatible federated social network. That means not one entity controls the whole network, rather, like e-mail, volunteers and organisations operate their own independent servers, users from which can all interact with each other seamlessly. -It is currently available on [Google Play](https://play.google.com/store/apps/details?id=com.keylesspalace.tusky). +[Get it on F-Droid](https://f-droid.org/repository/browse/?fdid=com.keylesspalace.tusky) +[Get it on Google Play](https://play.google.com/store/apps/details?id=com.keylesspalace.tusky&utm_source=github&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1) +[Available at Amazon](https://www.amazon.com/gp/product/B06ZYXT88G/ref=mas_pm_tusky) ## Features From 4442678fc481f905875ae69c256e60dc5dd6eeb5 Mon Sep 17 00:00:00 2001 From: Vavassor Date: Thu, 11 May 2017 16:16:31 -0400 Subject: [PATCH 41/53] Moves store badges and normalizes their sizes. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0b79a724..3d9e529b 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,6 @@ Tusky is a beautiful Android client for [Mastodon](https://github.com/tootsuite/mastodon). Mastodon is a GNU social-compatible federated social network. That means not one entity controls the whole network, rather, like e-mail, volunteers and organisations operate their own independent servers, users from which can all interact with each other seamlessly. -[Get it on F-Droid](https://f-droid.org/repository/browse/?fdid=com.keylesspalace.tusky) -[Get it on Google Play](https://play.google.com/store/apps/details?id=com.keylesspalace.tusky&utm_source=github&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1) -[Available at Amazon](https://www.amazon.com/gp/product/B06ZYXT88G/ref=mas_pm_tusky) - ## Features - Material Design @@ -18,6 +14,10 @@ Tusky is a beautiful Android client for [Mastodon](https://github.com/tootsuite/ My Mastodon account is [Vavassor@mastodon.social](https://mastodon.social/users/Vavassor). +[Get it on F-Droid](https://f-droid.org/repository/browse/?fdid=com.keylesspalace.tusky) +[Get it on Google Play](https://play.google.com/store/apps/details?id=com.keylesspalace.tusky&utm_source=github&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1) +[Available at Amazon](https://www.amazon.com/gp/product/B06ZYXT88G/ref=mas_pm_tusky) + ## Building The most basic things needed are the Java Development Kit 7 or higher and the Android SDK. From 0a41bed54faec421f9192839ba34c1a689412f60 Mon Sep 17 00:00:00 2001 From: Vavassor Date: Thu, 11 May 2017 16:29:36 -0400 Subject: [PATCH 42/53] Fixes the Amazon badge in README.md. --- README.md | 2 +- assets/amazon_badge.png | Bin 0 -> 8698 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 assets/amazon_badge.png diff --git a/README.md b/README.md index 3d9e529b..8bc62e88 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ My Mastodon account is [Vavassor@mastodon.social](https://mastodon.social/users/ [Get it on F-Droid](https://f-droid.org/repository/browse/?fdid=com.keylesspalace.tusky) [Get it on Google Play](https://play.google.com/store/apps/details?id=com.keylesspalace.tusky&utm_source=github&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1) -[Available at Amazon](https://www.amazon.com/gp/product/B06ZYXT88G/ref=mas_pm_tusky) +[![Available at Amazon](/assets/amazon_badge.png)](https://www.amazon.com/gp/product/B06ZYXT88G/ref=mas_pm_tusky) ## Building The most basic things needed are the Java Development Kit 7 or higher and the Android SDK. diff --git a/assets/amazon_badge.png b/assets/amazon_badge.png new file mode 100644 index 0000000000000000000000000000000000000000..e976e139860f0462494990d2988cdcf9285684e1 GIT binary patch literal 8698 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 literal 0 HcmV?d00001 From ecfa5aae8cde8cd3c83399072146aa5c1afb1a64 Mon Sep 17 00:00:00 2001 From: Vavassor Date: Thu, 11 May 2017 19:02:43 -0400 Subject: [PATCH 43/53] changes terminology --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 88cacbd1..68dc2768 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -160,7 +160,7 @@ Browser Use Chrome Custom Tabs Hide follow button while scrolling - Toot filtering + Timeline filtering Tabs Show boosts Show replies From 5249977a81d886ee000049b5cfd7836c5b70178d Mon Sep 17 00:00:00 2001 From: Vavassor Date: Thu, 11 May 2017 21:31:29 -0400 Subject: [PATCH 44/53] Makes the view media toolbar transparent. --- app/src/main/res/layout/fragment_view_media.xml | 5 ++--- app/src/main/res/values/colors.xml | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/layout/fragment_view_media.xml b/app/src/main/res/layout/fragment_view_media.xml index db7833b1..0faf6fe6 100644 --- a/app/src/main/res/layout/fragment_view_media.xml +++ b/app/src/main/res/layout/fragment_view_media.xml @@ -10,14 +10,13 @@ + android:layout_height="match_parent" /> #000000 + #8f000000 #4c5368 #363c4b From e0db0cfee5ea1d92c861f291923960529a193148 Mon Sep 17 00:00:00 2001 From: Vavassor Date: Tue, 16 May 2017 22:19:34 -0400 Subject: [PATCH 45/53] Initial client working for MQTT push notifications. --- app/build.gradle | 4 + app/src/main/AndroidManifest.xml | 4 + .../com/keylesspalace/tusky/BaseActivity.java | 43 +-- .../keylesspalace/tusky/ComposeActivity.java | 4 +- .../tusky/fragment/NotificationsFragment.java | 3 + .../service/PushNotificationService.java | 257 ++++++++++++++++++ .../tusky/util/PushNotificationClient.java | 256 +++++++++++++++++ app/src/main/res/values/donottranslate.xml | 1 + 8 files changed, 534 insertions(+), 38 deletions(-) create mode 100644 app/src/main/java/com/keylesspalace/tusky/service/PushNotificationService.java create mode 100644 app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java diff --git a/app/build.gradle b/app/build.gradle index cf42c979..5ef9205d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -63,6 +63,10 @@ dependencies { googleCompile 'com.google.firebase:firebase-crash:10.2.4' testCompile 'junit:junit:4.12' annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' + compile 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0' + compile('org.eclipse.paho:org.eclipse.paho.android.service:1.1.1') { + exclude module: 'support-v4' + } } apply plugin: 'com.google.gms.google-services' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 058d165b..1fd924b5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + + () { - @Override - public void onResponse(Call call, retrofit2.Response response) { - Log.d(TAG, "Enable push notifications response: " + response.message()); - } - - @Override - public void onFailure(Call call, Throwable t) { - Log.d(TAG, "Enable push notifications failed: " + t.getMessage()); - } - }); + pushNotificationClient.subscribeToTopic(); } else { // Start up the MessagingService on a repeating interval for "pull" notifications. long checkInterval = 60 * 1000 * 5; @@ -231,17 +210,7 @@ public class BaseActivity extends AppCompatActivity { protected void disablePushNotifications() { if (BuildConfig.USES_PUSH_NOTIFICATIONS) { - tuskyAPI.unregister(getBaseUrl(), getAccessToken()).enqueue(new Callback() { - @Override - public void onResponse(Call call, retrofit2.Response response) { - Log.d(TAG, "Disable push notifications response: " + response.message()); - } - - @Override - public void onFailure(Call call, Throwable t) { - Log.d(TAG, "Disable push notifications failed: " + t.getMessage()); - } - }); + pushNotificationClient.unsubscribeToTopic(); } else if (serviceAlarmIntent != null) { // Cancel the repeating call for "pull" notifications. AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); diff --git a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java index f0089fd2..319257b4 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java @@ -307,7 +307,9 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag 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"; diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java index bef04290..234ece7f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java @@ -233,6 +233,9 @@ public class NotificationsFragment extends SFragment implements } else { adapter.update(notifications); } + for (Notification notification : notifications) { + Log.d(TAG, "id: " + notification.id); + } if (notifications.size() == 0 && adapter.getItemCount() == 1) { adapter.setFooterState(NotificationsAdapter.FooterState.EMPTY); } else if (fromId != null) { diff --git a/app/src/main/java/com/keylesspalace/tusky/service/PushNotificationService.java b/app/src/main/java/com/keylesspalace/tusky/service/PushNotificationService.java new file mode 100644 index 00000000..7db3e124 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/service/PushNotificationService.java @@ -0,0 +1,257 @@ +package com.keylesspalace.tusky.service; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Binder; +import android.os.IBinder; +import android.preference.PreferenceManager; +import android.support.annotation.Nullable; +import android.text.Spanned; + +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 com.keylesspalace.tusky.network.MastodonAPI; +import com.keylesspalace.tusky.util.Log; +import com.keylesspalace.tusky.util.NotificationMaker; +import com.keylesspalace.tusky.util.OkHttpUtils; + +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.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; + +import java.io.IOException; +import java.util.Locale; +import java.util.UUID; + +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 PushNotificationService extends Service { + private class LocalBinder extends Binder { + PushNotificationService getService() { + return PushNotificationService.this; + } + } + + private static final String TAG = "PushNotificationService"; + private static final String CLIENT_NAME = "TuskyMastodonClient"; + private static final String TOPIC = "tusky/notification"; + private static final int NOTIFY_ID = 666; + + private final IBinder binder = new LocalBinder(); + private MqttAndroidClient mqttAndroidClient; + private MastodonAPI mastodonApi; + + @Override + public void onCreate() { + super.onCreate(); + + // Create the MQTT client. + String clientId = String.format(Locale.getDefault(), "%s/%s/%s", CLIENT_NAME, + System.currentTimeMillis(), UUID.randomUUID().toString()); + String serverUri = getString(R.string.tusky_api_url); + mqttAndroidClient = new MqttAndroidClient(this, serverUri, clientId); + mqttAndroidClient.setCallback(new MqttCallbackExtended() { + @Override + public void connectComplete(boolean reconnect, String serverURI) { + if (reconnect) { + subscribeToTopic(); + } + } + + @Override + public void connectionLost(Throwable cause) { + onConnectionLost(); + } + + @Override + public void messageArrived(String topic, MqttMessage message) throws Exception { + onMessageReceived(new String(message.getPayload())); + } + + @Override + public void deliveryComplete(IMqttDeliveryToken token) { + // This client is read-only, so this is unused. + } + }); + + // Open the MQTT connection. + MqttConnectOptions options = new MqttConnectOptions(); + options.setAutomaticReconnect(true); + options.setCleanSession(false); + try { + mqttAndroidClient.connect(options, null, new IMqttActionListener() { + @Override + public void onSuccess(IMqttToken asyncActionToken) { + DisconnectedBufferOptions options = new DisconnectedBufferOptions(); + options.setBufferEnabled(true); + options.setBufferSize(100); + options.setPersistBuffer(false); + options.setDeleteOldestMessages(false); + mqttAndroidClient.setBufferOpts(options); + onConnectionSuccess(); + subscribeToTopic(); + } + + @Override + public void onFailure(IMqttToken asyncActionToken, Throwable exception) { + onConnectionFailure(); + } + }); + } catch (MqttException e) { + Log.e(TAG, "An exception occurred while connecting. " + e.getMessage()); + onConnectionFailure(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + disconnect(); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return binder; + } + + /** Subscribe to the push notification topic. */ + public void subscribeToTopic() { + try { + mqttAndroidClient.subscribe(TOPIC, 0, null, new IMqttActionListener() { + @Override + public void onSuccess(IMqttToken asyncActionToken) { + onConnectionSuccess(); + } + + @Override + public void onFailure(IMqttToken asyncActionToken, Throwable exception) { + onConnectionFailure(); + } + }); + } catch (MqttException e) { + Log.e(TAG, "An exception occurred while subscribing." + e.getMessage()); + onConnectionFailure(); + } + } + + /** Unsubscribe from the push notification topic. */ + public void unsubscribeToTopic() { + try { + mqttAndroidClient.unsubscribe(TOPIC); + } catch (MqttException e) { + Log.e(TAG, "An exception occurred while unsubscribing." + e.getMessage()); + onConnectionFailure(); + } + } + + private void onConnectionSuccess() { + + } + + private void onConnectionFailure() { + + } + + private void onConnectionLost() { + + } + + private void onMessageReceived(String message) { + String notificationId = message; // TODO: finalize the form the messages will be received + + Log.d(TAG, "Notification received: " + notificationId); + + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences( + getApplicationContext()); + boolean enabled = preferences.getBoolean("notificationsEnabled", true); + if (!enabled) { + return; + } + + createMastodonAPI(); + + mastodonApi.notification(notificationId).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + NotificationMaker.make(PushNotificationService.this, NOTIFY_ID, + response.body()); + } + } + + @Override + public void onFailure(Call call, Throwable t) {} + }); + } + + /** Disconnect from the MQTT broker. */ + public void disconnect() { + try { + mqttAndroidClient.disconnect(); + } catch (MqttException ex) { + Log.e(TAG, "An exception occurred while disconnecting."); + onDisconnectFailed(); + } + } + + private void onDisconnectFailed() { + + } + + 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 = OkHttpUtils.getCompatibleClientBuilder() + .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()) + .registerTypeAdapter(StringWithEmoji.class, new StringWithEmojiTypeAdapter()) + .create(); + + Retrofit retrofit = new Retrofit.Builder() + .baseUrl("https://" + domain) + .client(okHttpClient) + .addConverterFactory(GsonConverterFactory.create(gson)) + .build(); + + mastodonApi = retrofit.create(MastodonAPI.class); + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java new file mode 100644 index 00000000..ba8f3450 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java @@ -0,0 +1,256 @@ +package com.keylesspalace.tusky.util; + +import android.content.Context; +import android.content.SharedPreferences; +import android.support.annotation.NonNull; +import android.text.Spanned; + +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 com.keylesspalace.tusky.network.MastodonAPI; + +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.IOException; +import java.util.ArrayDeque; + +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 PushNotificationClient { + private static final String TAG = "PushNotificationClient"; + private static final String TOPIC = "tusky/notification"; + private static final int NOTIFY_ID = 666; + + private enum QueuedAction { + SUBSCRIBE, + UNSUBSCRIBE, + DISCONNECT, + } + + private MqttAndroidClient mqttAndroidClient; + private MastodonAPI mastodonApi; + private boolean connected; + private ArrayDeque queuedActions; + + public PushNotificationClient(final @NonNull Context context, @NonNull String serverUri) { + queuedActions = new ArrayDeque<>(); + + // Create the MQTT client. + String clientId = MqttClient.generateClientId(); + mqttAndroidClient = new MqttAndroidClient(context, serverUri, clientId); + mqttAndroidClient.setCallback(new MqttCallbackExtended() { + @Override + public void connectComplete(boolean reconnect, String serverURI) { + if (reconnect) { + flushQueuedActions(); + } + } + + @Override + public void connectionLost(Throwable cause) { + onConnectionLost(); + } + + @Override + public void messageArrived(String topic, MqttMessage message) throws Exception { + onMessageReceived(context, new String(message.getPayload())); + } + + @Override + public void deliveryComplete(IMqttDeliveryToken token) { + // This client is read-only, so this is unused. + } + }); + + // Open the MQTT connection. + MqttConnectOptions options = new MqttConnectOptions(); + options.setAutomaticReconnect(true); + options.setCleanSession(false); + try { + /* TLS connection stuffs + InputStream input = context.getResources().openRawResource(R.raw.keystore_tusky_api); + String password = context.getString(R.string.tusky_api_keystore_password); + options.setSocketFactory(mqttAndroidClient.getSSLSocketFactory(input, password)); + */ + mqttAndroidClient.connect(options).setActionCallback(new IMqttActionListener() { + @Override + public void onSuccess(IMqttToken asyncActionToken) { + DisconnectedBufferOptions options = new DisconnectedBufferOptions(); + options.setBufferEnabled(true); + options.setBufferSize(100); + options.setPersistBuffer(false); + options.setDeleteOldestMessages(false); + mqttAndroidClient.setBufferOpts(options); + onConnectionSuccess(); + connected = true; + flushQueuedActions(); + } + + @Override + public void onFailure(IMqttToken asyncActionToken, Throwable exception) { + Log.e(TAG, "An exception occurred while connecting. " + exception.getMessage()); + onConnectionFailure(); + } + }); + } catch (MqttException e) { + Log.e(TAG, "An exception occurred while connecting. " + e.getMessage()); + onConnectionFailure(); + } + } + + private void flushQueuedActions() { + for (QueuedAction action : queuedActions) { + switch (action) { + case SUBSCRIBE: subscribeToTopic(); break; + case UNSUBSCRIBE: unsubscribeToTopic(); break; + case DISCONNECT: disconnect(); break; + } + } + } + + /** Disconnect from the MQTT broker. */ + public void disconnect() { + if (!connected) { + queuedActions.add(QueuedAction.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() { + if (!connected) { + queuedActions.add(QueuedAction.SUBSCRIBE); + return; + } + try { + mqttAndroidClient.subscribe(TOPIC, 0, null, new IMqttActionListener() { + @Override + public void onSuccess(IMqttToken asyncActionToken) { + 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() { + if (!connected) { + queuedActions.add(QueuedAction.UNSUBSCRIBE); + return; + } + try { + mqttAndroidClient.unsubscribe(TOPIC); + } catch (MqttException e) { + Log.e(TAG, "An exception occurred while unsubscribing." + 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."); + } + + private void onMessageReceived(final Context context, String message) { + String notificationId = message; // TODO: finalize the form the messages will be received + + Log.v(TAG, "Notification received: " + notificationId); + + createMastodonAPI(context); + + mastodonApi.notification(notificationId).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + NotificationMaker.make(context, NOTIFY_ID, response.body()); + } + } + + @Override + public void onFailure(Call call, Throwable t) {} + }); + } + + private void createMastodonAPI(Context context) { + SharedPreferences preferences = context.getSharedPreferences( + context.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 = OkHttpUtils.getCompatibleClientBuilder() + .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()) + .registerTypeAdapter(StringWithEmoji.class, new StringWithEmojiTypeAdapter()) + .create(); + + Retrofit retrofit = new Retrofit.Builder() + .baseUrl("https://" + domain) + .client(okHttpClient) + .addConverterFactory(GsonConverterFactory.create(gson)) + .build(); + + mastodonApi = retrofit.create(MastodonAPI.class); + } +} diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index b2a8e3b6..73a4b542 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -3,6 +3,7 @@ Tusky https://tusky.keylesspalace.com https://tuskynotifier.keylesspalace.com + your_password_here oauth2redirect com.keylesspalace.tusky From 54327f6e2be87cb738eb8cc4bef3ad68d2c7cf12 Mon Sep 17 00:00:00 2001 From: Vavassor Date: Thu, 18 May 2017 18:10:46 -0400 Subject: [PATCH 46/53] Removes the product flavor split. --- app/.gitignore | 1 + app/build.gradle | 16 +- app/google-services.json | 55 ------- app/src/fdroid/AndroidManifest.xml | 12 -- .../keylesspalace/tusky/MessagingService.java | 142 ------------------ app/src/google/AndroidManifest.xml | 18 --- .../keylesspalace/tusky/MessagingService.java | 133 ---------------- .../tusky/MyFirebaseInstanceIdService.java | 86 ----------- .../com/keylesspalace/tusky/BaseActivity.java | 32 +--- .../com/keylesspalace/tusky/MainActivity.java | 3 +- .../tusky/util/PushNotificationClient.java | 27 ++-- app/src/main/res/values/donottranslate.xml | 2 +- build.gradle | 1 - 13 files changed, 27 insertions(+), 501 deletions(-) delete mode 100644 app/google-services.json delete mode 100644 app/src/fdroid/AndroidManifest.xml delete mode 100644 app/src/fdroid/java/com/keylesspalace/tusky/MessagingService.java delete mode 100644 app/src/google/AndroidManifest.xml delete mode 100644 app/src/google/java/com/keylesspalace/tusky/MessagingService.java delete mode 100644 app/src/google/java/com/keylesspalace/tusky/MyFirebaseInstanceIdService.java diff --git a/app/.gitignore b/app/.gitignore index 61e52b44..d4026ab8 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1,3 +1,4 @@ /build app-release.apk app-google-release.apk +src/main/res/raw/keystore_tusky_api.bks diff --git a/app/build.gradle b/app/build.gradle index 5ef9205d..cb33758a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,14 +12,6 @@ android { testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary true } - productFlavors { - google { - buildConfigField "boolean", "USES_PUSH_NOTIFICATIONS", "true" - } - fdroid { - buildConfigField "boolean", "USES_PUSH_NOTIFICATIONS", "false" - } - } buildTypes { release { minifyEnabled true @@ -59,14 +51,10 @@ dependencies { compile 'com.github.arimorty:floatingsearchview:2.0.4' compile 'com.theartofdev.edmodo:android-image-cropper:2.4.3' compile 'com.jakewharton:butterknife:8.5.1' - googleCompile 'com.google.firebase:firebase-messaging:10.2.4' - googleCompile 'com.google.firebase:firebase-crash:10.2.4' - testCompile 'junit:junit:4.12' - annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' compile 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0' compile('org.eclipse.paho:org.eclipse.paho.android.service:1.1.1') { exclude module: 'support-v4' } + testCompile 'junit:junit:4.12' + annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' } - -apply plugin: 'com.google.gms.google-services' diff --git a/app/google-services.json b/app/google-services.json deleted file mode 100644 index bfd19f88..00000000 --- a/app/google-services.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "project_info": { - "project_number": "268851337880", - "firebase_url": "https://tusky-62772.firebaseio.com", - "project_id": "tusky-62772", - "storage_bucket": "tusky-62772.appspot.com" - }, - "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:268851337880:android:fc4111b1d145a00e", - "android_client_info": { - "package_name": "com.keylesspalace.tusky" - } - }, - "oauth_client": [ - { - "client_id": "268851337880-eie2ssto2d21bfihn9d1qupcrke8oebf.apps.googleusercontent.com", - "client_type": 1, - "android_info": { - "package_name": "com.keylesspalace.tusky", - "certificate_hash": "18d196307d6e928e99c2e0bb9818c01c38aff2f9" - } - }, - { - "client_id": "268851337880-n19d05m282nirs1fc9kdd5n4of6je4fk.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyCbJtSjuk4I3Jy8PdUaO3TaQOXubcOUElo" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "appinvite_service": { - "status": 2, - "other_platform_oauth_client": [ - { - "client_id": "268851337880-n19d05m282nirs1fc9kdd5n4of6je4fk.apps.googleusercontent.com", - "client_type": 3 - } - ] - }, - "ads_service": { - "status": 2 - } - } - } - ], - "configuration_version": "1" -} \ No newline at end of file diff --git a/app/src/fdroid/AndroidManifest.xml b/app/src/fdroid/AndroidManifest.xml deleted file mode 100644 index b7060e6e..00000000 --- a/app/src/fdroid/AndroidManifest.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/fdroid/java/com/keylesspalace/tusky/MessagingService.java b/app/src/fdroid/java/com/keylesspalace/tusky/MessagingService.java deleted file mode 100644 index eaa1b1b5..00000000 --- a/app/src/fdroid/java/com/keylesspalace/tusky/MessagingService.java +++ /dev/null @@ -1,142 +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 . */ - -package com.keylesspalace.tusky; - -import android.app.IntentService; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import android.text.Spanned; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - -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 com.keylesspalace.tusky.network.MastodonAPI; -import com.keylesspalace.tusky.util.NotificationMaker; -import com.keylesspalace.tusky.util.OkHttpUtils; - -import java.util.HashSet; -import java.util.List; - -import java.io.IOException; -import java.util.Set; - -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 MessagingService extends IntentService { - public static final int NOTIFY_ID = 6; // This is an arbitrary number. - - private MastodonAPI mastodonAPI; - - public MessagingService() { - super("Tusky Pull Notification Service"); - } - - @Override - protected void onHandleIntent(Intent intent) { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences( - getApplicationContext()); - boolean enabled = preferences.getBoolean("notificationsEnabled", true); - if (!enabled) { - return; - } - - createMastodonApi(); - - mastodonAPI.notifications(null, null, null).enqueue(new Callback>() { - @Override - public void onResponse(Call> call, - Response> response) { - if (response.isSuccessful()) { - onNotificationsReceived(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 = OkHttpUtils.getCompatibleClientBuilder() - .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()) - .registerTypeAdapter(StringWithEmoji.class, new StringWithEmojiTypeAdapter()) - .create(); - - Retrofit retrofit = new Retrofit.Builder() - .baseUrl("https://" + domain) - .client(okHttpClient) - .addConverterFactory(GsonConverterFactory.create(gson)) - .build(); - - mastodonAPI = retrofit.create(MastodonAPI.class); - } - - private void onNotificationsReceived(List notificationList) { - SharedPreferences notificationsPreferences = getSharedPreferences( - "Notifications", Context.MODE_PRIVATE); - Set currentIds = notificationsPreferences.getStringSet( - "current_ids", new HashSet()); - for (Notification notification : notificationList) { - String id = notification.id; - if (!currentIds.contains(id)) { - currentIds.add(id); - NotificationMaker.make(this, NOTIFY_ID, notification); - } - } - notificationsPreferences.edit() - .putStringSet("current_ids", currentIds) - .apply(); - } - - public static String getInstanceToken() { - // This is only used for the "google" build flavor, so this version is just a stub method. - return null; - } -} diff --git a/app/src/google/AndroidManifest.xml b/app/src/google/AndroidManifest.xml deleted file mode 100644 index 20ecbe94..00000000 --- a/app/src/google/AndroidManifest.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/app/src/google/java/com/keylesspalace/tusky/MessagingService.java b/app/src/google/java/com/keylesspalace/tusky/MessagingService.java deleted file mode 100644 index 4a2a55da..00000000 --- a/app/src/google/java/com/keylesspalace/tusky/MessagingService.java +++ /dev/null @@ -1,133 +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 . - * - * If you modify this Program, or any covered work, by linking or combining it with Firebase Cloud - * Messaging and Firebase Crash Reporting (or a modified version of those libraries), containing - * parts covered by the Google APIs Terms of Service, the licensors of this Program grant you - * additional permission to convey the resulting work. */ - -package com.keylesspalace.tusky; - -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import android.text.Spanned; - -import com.google.firebase.iid.FirebaseInstanceId; -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.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.util.Log; -import com.keylesspalace.tusky.util.NotificationMaker; -import com.keylesspalace.tusky.util.OkHttpUtils; - -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 MessagingService extends FirebaseMessagingService { - private MastodonAPI mastodonAPI; - private static final String TAG = "MessagingService"; - public static final int NOTIFY_ID = 666; - - @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); - - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences( - getApplicationContext()); - boolean enabled = preferences.getBoolean("notificationsEnabled", true); - if (!enabled) { - return; - } - - createMastodonAPI(); - - mastodonAPI.notification(notificationId).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - NotificationMaker.make(MessagingService.this, NOTIFY_ID, 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 = OkHttpUtils.getCompatibleClientBuilder() - .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()) - .registerTypeAdapter(StringWithEmoji.class, new StringWithEmojiTypeAdapter()) - .create(); - - Retrofit retrofit = new Retrofit.Builder() - .baseUrl("https://" + domain) - .client(okHttpClient) - .addConverterFactory(GsonConverterFactory.create(gson)) - .build(); - - mastodonAPI = retrofit.create(MastodonAPI.class); - } - - public static String getInstanceToken() { - return FirebaseInstanceId.getInstance().getToken(); - } -} diff --git a/app/src/google/java/com/keylesspalace/tusky/MyFirebaseInstanceIdService.java b/app/src/google/java/com/keylesspalace/tusky/MyFirebaseInstanceIdService.java deleted file mode 100644 index 14d640d5..00000000 --- a/app/src/google/java/com/keylesspalace/tusky/MyFirebaseInstanceIdService.java +++ /dev/null @@ -1,86 +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 . - * - * If you modify this Program, or any covered work, by linking or combining it with Firebase Cloud - * Messaging and Firebase Crash Reporting (or a modified version of those libraries), containing - * parts covered by the Google APIs Terms of Service, the licensors of this Program grant you - * additional permission to convey the resulting work. */ - -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 com.keylesspalace.tusky.network.TuskyAPI; -import com.keylesspalace.tusky.util.Log; -import com.keylesspalace.tusky.util.OkHttpUtils; - -import okhttp3.ResponseBody; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; -import retrofit2.Retrofit; - -public class MyFirebaseInstanceIdService extends FirebaseInstanceIdService { - private static final String TAG = "com.keylesspalace.tusky.MyFirebaseInstanceIdService"; - - private TuskyAPI tuskyAPI; - - protected void createTuskyAPI() { - Retrofit retrofit = new Retrofit.Builder() - .baseUrl(getString(R.string.tusky_api_url)) - .client(OkHttpUtils.getCompatibleClient()) - .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) { - Log.d(TAG, response.message()); - } - - @Override - public void onFailure(Call call, Throwable t) { - Log.d(TAG, t.getMessage()); - } - }); - tuskyAPI.register("https://" + domain, accessToken, refreshedToken).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - Log.d(TAG, response.message()); - } - - @Override - public void onFailure(Call call, Throwable t) { - Log.d(TAG, t.getMessage()); - } - }); - } - } -} diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index 4ba4c0b8..e88cd1cb 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -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,7 +22,6 @@ 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; @@ -55,7 +52,6 @@ public class BaseActivity extends AppCompatActivity { public MastodonAPI mastodonAPI; protected PushNotificationClient pushNotificationClient; protected Dispatcher mastodonApiDispatcher; - protected PendingIntent serviceAlarmIntent; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -156,10 +152,8 @@ public class BaseActivity extends AppCompatActivity { } protected void createTuskyAPI() { - if (BuildConfig.USES_PUSH_NOTIFICATIONS) { - // TODO: Remove this test broker address. - pushNotificationClient = new PushNotificationClient(this, "tcp://104.236.116.199:1883"); - } + pushNotificationClient = new PushNotificationClient(this, + getString(R.string.tusky_api_url)); } protected void redirectIfNotLoggedIn() { @@ -193,28 +187,10 @@ public class BaseActivity extends AppCompatActivity { } protected void enablePushNotifications() { - if (BuildConfig.USES_PUSH_NOTIFICATIONS) { - pushNotificationClient.subscribeToTopic(); - } 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); - } + pushNotificationClient.subscribeToTopic(); } protected void disablePushNotifications() { - if (BuildConfig.USES_PUSH_NOTIFICATIONS) { - pushNotificationClient.unsubscribeToTopic(); - } else if (serviceAlarmIntent != null) { - // Cancel the repeating call for "pull" notifications. - AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - alarmManager.cancel(serviceAlarmIntent); - } + pushNotificationClient.unsubscribeToTopic(); } } diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java index 3d39b81a..4c4c2db5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java @@ -215,8 +215,7 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove .putString("current", "[]") .apply(); - ((NotificationManager) (getSystemService(NOTIFICATION_SERVICE))) - .cancel(MessagingService.NOTIFY_ID); + pushNotificationClient.clearNotifications(); /* After editing a profile, the profile header in the navigation drawer needs to be * refreshed */ diff --git a/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java index ba8f3450..6c944f75 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java @@ -26,6 +26,7 @@ import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import java.io.IOException; +import java.io.InputStream; import java.util.ArrayDeque; import okhttp3.Interceptor; @@ -88,20 +89,24 @@ public class PushNotificationClient { options.setAutomaticReconnect(true); options.setCleanSession(false); try { - /* TLS connection stuffs - InputStream input = context.getResources().openRawResource(R.raw.keystore_tusky_api); + /* String password = context.getString(R.string.tusky_api_keystore_password); - options.setSocketFactory(mqttAndroidClient.getSSLSocketFactory(input, 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 options = new DisconnectedBufferOptions(); - options.setBufferEnabled(true); - options.setBufferSize(100); - options.setPersistBuffer(false); - options.setDeleteOldestMessages(false); - mqttAndroidClient.setBufferOpts(options); + DisconnectedBufferOptions bufferOptions = new DisconnectedBufferOptions(); + bufferOptions.setBufferEnabled(true); + bufferOptions.setBufferSize(100); + bufferOptions.setPersistBuffer(false); + bufferOptions.setDeleteOldestMessages(false); + mqttAndroidClient.setBufferOpts(bufferOptions); onConnectionSuccess(); connected = true; flushQueuedActions(); @@ -253,4 +258,8 @@ public class PushNotificationClient { mastodonApi = retrofit.create(MastodonAPI.class); } + + public void clearNotifications() { + // TODO: make it happen + } } diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 73a4b542..212d54db 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -2,7 +2,7 @@ Tusky https://tusky.keylesspalace.com - https://tuskynotifier.keylesspalace.com + tcp://tuskyapi.keylesspalace.com your_password_here oauth2redirect diff --git a/build.gradle b/build.gradle index 80e6fd50..bfed5920 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,6 @@ buildscript { // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files - classpath 'com.google.gms:google-services:3.0.0' } } From 6208b848b749cffafc9e002b6399c7b4bb41e243 Mon Sep 17 00:00:00 2001 From: Vavassor Date: Fri, 19 May 2017 20:00:57 -0400 Subject: [PATCH 47/53] Adds notification clearing and makes the client a little more stable. --- app/src/main/AndroidManifest.xml | 12 +++---- .../com/keylesspalace/tusky/BaseActivity.java | 2 +- .../com/keylesspalace/tusky/MainActivity.java | 3 +- .../tusky/util/PushNotificationClient.java | 31 ++++++++++++------- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1fd924b5..cbe15faa 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,18 +6,18 @@ - - - - + + + + + android:theme="@style/AppTheme"> queuedActions; + private boolean subscribed; - public PushNotificationClient(final @NonNull Context context, @NonNull String serverUri) { + public PushNotificationClient(final @NonNull Context applicationContext, + @NonNull String serverUri) { queuedActions = new ArrayDeque<>(); // Create the MQTT client. String clientId = MqttClient.generateClientId(); - mqttAndroidClient = new MqttAndroidClient(context, serverUri, clientId); + mqttAndroidClient = new MqttAndroidClient(applicationContext, serverUri, clientId); mqttAndroidClient.setCallback(new MqttCallbackExtended() { @Override public void connectComplete(boolean reconnect, String serverURI) { if (reconnect) { flushQueuedActions(); + if (subscribed) { + subscribeToTopic(); + } } } @@ -75,7 +82,7 @@ public class PushNotificationClient { @Override public void messageArrived(String topic, MqttMessage message) throws Exception { - onMessageReceived(context, new String(message.getPayload())); + onMessageReceived(applicationContext, new String(message.getPayload())); } @Override @@ -108,7 +115,6 @@ public class PushNotificationClient { bufferOptions.setDeleteOldestMessages(false); mqttAndroidClient.setBufferOpts(bufferOptions); onConnectionSuccess(); - connected = true; flushQueuedActions(); } @@ -125,7 +131,8 @@ public class PushNotificationClient { } private void flushQueuedActions() { - for (QueuedAction action : queuedActions) { + while (!queuedActions.isEmpty()) { + QueuedAction action = queuedActions.pop(); switch (action) { case SUBSCRIBE: subscribeToTopic(); break; case UNSUBSCRIBE: unsubscribeToTopic(); break; @@ -136,7 +143,7 @@ public class PushNotificationClient { /** Disconnect from the MQTT broker. */ public void disconnect() { - if (!connected) { + if (!mqttAndroidClient.isConnected()) { queuedActions.add(QueuedAction.DISCONNECT); return; } @@ -154,7 +161,7 @@ public class PushNotificationClient { /** Subscribe to the push notification topic. */ public void subscribeToTopic() { - if (!connected) { + if (!mqttAndroidClient.isConnected()) { queuedActions.add(QueuedAction.SUBSCRIBE); return; } @@ -162,6 +169,7 @@ public class PushNotificationClient { mqttAndroidClient.subscribe(TOPIC, 0, null, new IMqttActionListener() { @Override public void onSuccess(IMqttToken asyncActionToken) { + subscribed = true; onConnectionSuccess(); } @@ -179,12 +187,13 @@ public class PushNotificationClient { /** Unsubscribe from the push notification topic. */ public void unsubscribeToTopic() { - if (!connected) { + if (!mqttAndroidClient.isConnected()) { queuedActions.add(QueuedAction.UNSUBSCRIBE); return; } try { mqttAndroidClient.unsubscribe(TOPIC); + subscribed = false; } catch (MqttException e) { Log.e(TAG, "An exception occurred while unsubscribing." + e.getMessage()); onConnectionFailure(); @@ -259,7 +268,7 @@ public class PushNotificationClient { mastodonApi = retrofit.create(MastodonAPI.class); } - public void clearNotifications() { - // TODO: make it happen + public void clearNotifications(Context context) { + ((NotificationManager) (context.getSystemService(NOTIFICATION_SERVICE))).cancel(NOTIFY_ID); } } From 3744bb1a60f03651607950af22ec8c5d941979c8 Mon Sep 17 00:00:00 2001 From: Vavassor Date: Fri, 19 May 2017 21:28:12 -0400 Subject: [PATCH 48/53] Setup client-side for integration with the wryk/tusky-api prototype. --- .../com/keylesspalace/tusky/BaseActivity.java | 76 +++++++++++++++++-- .../network/{TuskyAPI.java => TuskyApi.java} | 7 +- .../tusky/util/PushNotificationClient.java | 61 ++++++++++----- app/src/main/res/values/donottranslate.xml | 2 +- 4 files changed, 115 insertions(+), 31 deletions(-) rename app/src/main/java/com/keylesspalace/tusky/network/{TuskyAPI.java => TuskyApi.java} (73%) diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index 0bb76039..d01eb28f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -35,6 +35,8 @@ 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.util.OkHttpUtils; import com.keylesspalace.tusky.util.PushNotificationClient; @@ -45,11 +47,17 @@ import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.Callback; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; public class BaseActivity extends AppCompatActivity { + private static final String TAG = "BaseActivity"; // logging tag + public MastodonAPI mastodonAPI; + public TuskyApi tuskyApi; protected PushNotificationClient pushNotificationClient; protected Dispatcher mastodonApiDispatcher; @@ -59,7 +67,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 @@ -151,9 +160,19 @@ public class BaseActivity extends AppCompatActivity { mastodonAPI = retrofit.create(MastodonAPI.class); } - protected void createTuskyAPI() { + protected void createTuskyApi() { + Retrofit retrofit = new Retrofit.Builder() + .baseUrl("https://" + getString(R.string.tusky_api_domain)) + .client(OkHttpUtils.getCompatibleClient()) + .build(); + + tuskyApi = retrofit.create(TuskyApi.class); + } + + protected void createPushNotificationClient() { + // TODO: Switch to ssl:// when TLS support is added. pushNotificationClient = new PushNotificationClient(getApplicationContext(), - getString(R.string.tusky_api_url)); + "tcp://" + getString(R.string.tusky_api_domain)); } protected void redirectIfNotLoggedIn() { @@ -187,10 +206,57 @@ public class BaseActivity extends AppCompatActivity { } protected void enablePushNotifications() { - pushNotificationClient.subscribeToTopic(); + Callback callback = new Callback() { + @Override + public void onResponse(Call call, + retrofit2.Response response) { + if (response.isSuccessful()) { + pushNotificationClient.subscribeToTopic(getPushNotificationTopic()); + } else { + onEnablePushNotificationsFailure(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + onEnablePushNotificationsFailure(); + } + }; + tuskyApi.register(getBaseUrl(), getAccessToken(), pushNotificationClient.getDeviceToken()) + .enqueue(callback); + } + + private void onEnablePushNotificationsFailure() { + Log.e(TAG, "Enabling push notifications failed."); } protected void disablePushNotifications() { - pushNotificationClient.unsubscribeToTopic(); + Callback callback = new Callback() { + @Override + public void onResponse(Call call, + retrofit2.Response response) { + if (response.isSuccessful()) { + pushNotificationClient.unsubscribeToTopic(getPushNotificationTopic()); + } else { + onDisablePushNotificationsFailure(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + onDisablePushNotificationsFailure(); + } + }; + tuskyApi.unregister(getBaseUrl(), getAccessToken(), pushNotificationClient.getDeviceToken()) + .enqueue(callback); + } + + private void onDisablePushNotificationsFailure() { + Log.e(TAG, "Disabling push notifications failed."); + } + + private String getPushNotificationTopic() { + return String.format("%s/%s/%s", getBaseUrl(), getAccessToken(), + pushNotificationClient.getDeviceToken()); } } diff --git a/app/src/main/java/com/keylesspalace/tusky/network/TuskyAPI.java b/app/src/main/java/com/keylesspalace/tusky/network/TuskyApi.java similarity index 73% rename from app/src/main/java/com/keylesspalace/tusky/network/TuskyAPI.java rename to app/src/main/java/com/keylesspalace/tusky/network/TuskyApi.java index 700de83c..393decb9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/TuskyAPI.java +++ b/app/src/main/java/com/keylesspalace/tusky/network/TuskyApi.java @@ -17,15 +17,14 @@ package com.keylesspalace.tusky.network; import okhttp3.ResponseBody; import retrofit2.Call; -import retrofit2.http.Field; import retrofit2.http.FormUrlEncoded; import retrofit2.http.POST; -public interface TuskyAPI { +public interface TuskyApi { @FormUrlEncoded @POST("/register") - Call register(@Field("instance_url") String instanceUrl, @Field("access_token") String accessToken, @Field("device_token") String deviceToken); + Call register(String instanceUrl, String accessToken, String deviceToken); @FormUrlEncoded @POST("/unregister") - Call unregister(@Field("instance_url") String instanceUrl, @Field("access_token") String accessToken); + Call unregister(String instanceUrl, String accessToken, String deviceToken); } diff --git a/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java index f75e318e..40150bb2 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java @@ -29,6 +29,7 @@ import org.eclipse.paho.client.mqttv3.MqttMessage; import java.io.IOException; import java.io.InputStream; import java.util.ArrayDeque; +import java.util.ArrayList; import okhttp3.Interceptor; import okhttp3.OkHttpClient; @@ -43,23 +44,37 @@ import static android.content.Context.NOTIFICATION_SERVICE; public class PushNotificationClient { private static final String TAG = "PushNotificationClient"; - private static final String TOPIC = "tusky/notification"; private static final int NOTIFY_ID = 666; - private enum QueuedAction { - SUBSCRIBE, - UNSUBSCRIBE, - DISCONNECT, + 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 MastodonAPI mastodonApi; private ArrayDeque queuedActions; - private boolean subscribed; + private ArrayList subscribedTopics; public PushNotificationClient(final @NonNull Context applicationContext, @NonNull String serverUri) { queuedActions = new ArrayDeque<>(); + subscribedTopics = new ArrayList<>(); // Create the MQTT client. String clientId = MqttClient.generateClientId(); @@ -69,8 +84,8 @@ public class PushNotificationClient { public void connectComplete(boolean reconnect, String serverURI) { if (reconnect) { flushQueuedActions(); - if (subscribed) { - subscribeToTopic(); + for (String topic : subscribedTopics) { + subscribeToTopic(topic); } } } @@ -133,10 +148,10 @@ public class PushNotificationClient { private void flushQueuedActions() { while (!queuedActions.isEmpty()) { QueuedAction action = queuedActions.pop(); - switch (action) { - case SUBSCRIBE: subscribeToTopic(); break; - case UNSUBSCRIBE: unsubscribeToTopic(); break; - case DISCONNECT: disconnect(); break; + switch (action.type) { + case SUBSCRIBE: subscribeToTopic(action.topic); break; + case UNSUBSCRIBE: unsubscribeToTopic(action.topic); break; + case DISCONNECT: disconnect(); break; } } } @@ -144,7 +159,7 @@ public class PushNotificationClient { /** Disconnect from the MQTT broker. */ public void disconnect() { if (!mqttAndroidClient.isConnected()) { - queuedActions.add(QueuedAction.DISCONNECT); + queuedActions.add(new QueuedAction(QueuedAction.Type.DISCONNECT)); return; } try { @@ -160,16 +175,16 @@ public class PushNotificationClient { } /** Subscribe to the push notification topic. */ - public void subscribeToTopic() { + public void subscribeToTopic(final String topic) { if (!mqttAndroidClient.isConnected()) { - queuedActions.add(QueuedAction.SUBSCRIBE); + queuedActions.add(new QueuedAction(QueuedAction.Type.SUBSCRIBE, topic)); return; } try { - mqttAndroidClient.subscribe(TOPIC, 0, null, new IMqttActionListener() { + mqttAndroidClient.subscribe(topic, 0, null, new IMqttActionListener() { @Override public void onSuccess(IMqttToken asyncActionToken) { - subscribed = true; + subscribedTopics.add(topic); onConnectionSuccess(); } @@ -186,14 +201,14 @@ public class PushNotificationClient { } /** Unsubscribe from the push notification topic. */ - public void unsubscribeToTopic() { + public void unsubscribeToTopic(String topic) { if (!mqttAndroidClient.isConnected()) { - queuedActions.add(QueuedAction.UNSUBSCRIBE); + queuedActions.add(new QueuedAction(QueuedAction.Type.UNSUBSCRIBE, topic)); return; } try { - mqttAndroidClient.unsubscribe(TOPIC); - subscribed = false; + mqttAndroidClient.unsubscribe(topic); + subscribedTopics.remove(topic); } catch (MqttException e) { Log.e(TAG, "An exception occurred while unsubscribing." + e.getMessage()); onConnectionFailure(); @@ -271,4 +286,8 @@ public class PushNotificationClient { public void clearNotifications(Context context) { ((NotificationManager) (context.getSystemService(NOTIFICATION_SERVICE))).cancel(NOTIFY_ID); } + + public String getDeviceToken() { + return mqttAndroidClient.getClientId(); + } } diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 212d54db..e6982534 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -2,7 +2,7 @@ Tusky https://tusky.keylesspalace.com - tcp://tuskyapi.keylesspalace.com + tuskyapi.keylesspalace.com your_password_here oauth2redirect From 623af5fd9b2c1f235bbbb4e97ff45bc8d396c2dd Mon Sep 17 00:00:00 2001 From: Vavassor Date: Sat, 20 May 2017 02:39:29 -0400 Subject: [PATCH 49/53] Integrates with wryk/tusky-api, but only partially working. Registers to the web-service fine but loses connection when subscribing with the broker. --- .../com/keylesspalace/tusky/BaseActivity.java | 23 +- .../keylesspalace/tusky/entity/Session.java | 28 ++ .../keylesspalace/tusky/network/TuskyApi.java | 10 +- .../service/PushNotificationService.java | 257 ------------------ .../tusky/util/PushNotificationClient.java | 48 ++-- 5 files changed, 75 insertions(+), 291 deletions(-) create mode 100644 app/src/main/java/com/keylesspalace/tusky/entity/Session.java delete mode 100644 app/src/main/java/com/keylesspalace/tusky/service/PushNotificationService.java diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index d01eb28f..da1656fc 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -31,6 +31,7 @@ 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; @@ -162,8 +163,9 @@ public class BaseActivity extends AppCompatActivity { protected void createTuskyApi() { Retrofit retrofit = new Retrofit.Builder() - .baseUrl("https://" + getString(R.string.tusky_api_domain)) + .baseUrl("http://" + getString(R.string.tusky_api_domain) + ":8080") .client(OkHttpUtils.getCompatibleClient()) + .addConverterFactory(GsonConverterFactory.create()) .build(); tuskyApi = retrofit.create(TuskyApi.class); @@ -172,7 +174,7 @@ public class BaseActivity extends AppCompatActivity { protected void createPushNotificationClient() { // TODO: Switch to ssl:// when TLS support is added. pushNotificationClient = new PushNotificationClient(getApplicationContext(), - "tcp://" + getString(R.string.tusky_api_domain)); + "tcp://" + getString(R.string.tusky_api_domain) + ":8000"); } protected void redirectIfNotLoggedIn() { @@ -212,6 +214,7 @@ public class BaseActivity extends AppCompatActivity { retrofit2.Response response) { if (response.isSuccessful()) { pushNotificationClient.subscribeToTopic(getPushNotificationTopic()); + pushNotificationClient.connect(); } else { onEnablePushNotificationsFailure(); } @@ -222,7 +225,9 @@ public class BaseActivity extends AppCompatActivity { onEnablePushNotificationsFailure(); } }; - tuskyApi.register(getBaseUrl(), getAccessToken(), pushNotificationClient.getDeviceToken()) + String deviceToken = pushNotificationClient.getDeviceToken(); + Session session = new Session(getDomain(), getAccessToken(), deviceToken); + tuskyApi.register(session) .enqueue(callback); } @@ -247,7 +252,9 @@ public class BaseActivity extends AppCompatActivity { onDisablePushNotificationsFailure(); } }; - tuskyApi.unregister(getBaseUrl(), getAccessToken(), pushNotificationClient.getDeviceToken()) + String deviceToken = pushNotificationClient.getDeviceToken(); + Session session = new Session(getDomain(), getAccessToken(), deviceToken); + tuskyApi.unregister(session) .enqueue(callback); } @@ -256,7 +263,11 @@ public class BaseActivity extends AppCompatActivity { } private String getPushNotificationTopic() { - return String.format("%s/%s/%s", getBaseUrl(), getAccessToken(), - pushNotificationClient.getDeviceToken()); + return String.format("%s/%s/#", getDomain(), getAccessToken()); + } + + private String getDomain() { + return getPrivatePreferences() + .getString("domain", null); } } diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Session.java b/app/src/main/java/com/keylesspalace/tusky/entity/Session.java new file mode 100644 index 00000000..8a3c5d0b --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/entity/Session.java @@ -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 . */ + +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; + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/network/TuskyApi.java b/app/src/main/java/com/keylesspalace/tusky/network/TuskyApi.java index 393decb9..24384dcf 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/TuskyApi.java +++ b/app/src/main/java/com/keylesspalace/tusky/network/TuskyApi.java @@ -15,16 +15,16 @@ package com.keylesspalace.tusky.network; +import com.keylesspalace.tusky.entity.Session; + import okhttp3.ResponseBody; import retrofit2.Call; -import retrofit2.http.FormUrlEncoded; +import retrofit2.http.Body; import retrofit2.http.POST; public interface TuskyApi { - @FormUrlEncoded @POST("/register") - Call register(String instanceUrl, String accessToken, String deviceToken); - @FormUrlEncoded + Call register(@Body Session session); @POST("/unregister") - Call unregister(String instanceUrl, String accessToken, String deviceToken); + Call unregister(@Body Session session); } diff --git a/app/src/main/java/com/keylesspalace/tusky/service/PushNotificationService.java b/app/src/main/java/com/keylesspalace/tusky/service/PushNotificationService.java deleted file mode 100644 index 7db3e124..00000000 --- a/app/src/main/java/com/keylesspalace/tusky/service/PushNotificationService.java +++ /dev/null @@ -1,257 +0,0 @@ -package com.keylesspalace.tusky.service; - -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Binder; -import android.os.IBinder; -import android.preference.PreferenceManager; -import android.support.annotation.Nullable; -import android.text.Spanned; - -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 com.keylesspalace.tusky.network.MastodonAPI; -import com.keylesspalace.tusky.util.Log; -import com.keylesspalace.tusky.util.NotificationMaker; -import com.keylesspalace.tusky.util.OkHttpUtils; - -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.MqttConnectOptions; -import org.eclipse.paho.client.mqttv3.MqttException; -import org.eclipse.paho.client.mqttv3.MqttMessage; - -import java.io.IOException; -import java.util.Locale; -import java.util.UUID; - -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 PushNotificationService extends Service { - private class LocalBinder extends Binder { - PushNotificationService getService() { - return PushNotificationService.this; - } - } - - private static final String TAG = "PushNotificationService"; - private static final String CLIENT_NAME = "TuskyMastodonClient"; - private static final String TOPIC = "tusky/notification"; - private static final int NOTIFY_ID = 666; - - private final IBinder binder = new LocalBinder(); - private MqttAndroidClient mqttAndroidClient; - private MastodonAPI mastodonApi; - - @Override - public void onCreate() { - super.onCreate(); - - // Create the MQTT client. - String clientId = String.format(Locale.getDefault(), "%s/%s/%s", CLIENT_NAME, - System.currentTimeMillis(), UUID.randomUUID().toString()); - String serverUri = getString(R.string.tusky_api_url); - mqttAndroidClient = new MqttAndroidClient(this, serverUri, clientId); - mqttAndroidClient.setCallback(new MqttCallbackExtended() { - @Override - public void connectComplete(boolean reconnect, String serverURI) { - if (reconnect) { - subscribeToTopic(); - } - } - - @Override - public void connectionLost(Throwable cause) { - onConnectionLost(); - } - - @Override - public void messageArrived(String topic, MqttMessage message) throws Exception { - onMessageReceived(new String(message.getPayload())); - } - - @Override - public void deliveryComplete(IMqttDeliveryToken token) { - // This client is read-only, so this is unused. - } - }); - - // Open the MQTT connection. - MqttConnectOptions options = new MqttConnectOptions(); - options.setAutomaticReconnect(true); - options.setCleanSession(false); - try { - mqttAndroidClient.connect(options, null, new IMqttActionListener() { - @Override - public void onSuccess(IMqttToken asyncActionToken) { - DisconnectedBufferOptions options = new DisconnectedBufferOptions(); - options.setBufferEnabled(true); - options.setBufferSize(100); - options.setPersistBuffer(false); - options.setDeleteOldestMessages(false); - mqttAndroidClient.setBufferOpts(options); - onConnectionSuccess(); - subscribeToTopic(); - } - - @Override - public void onFailure(IMqttToken asyncActionToken, Throwable exception) { - onConnectionFailure(); - } - }); - } catch (MqttException e) { - Log.e(TAG, "An exception occurred while connecting. " + e.getMessage()); - onConnectionFailure(); - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - disconnect(); - } - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return binder; - } - - /** Subscribe to the push notification topic. */ - public void subscribeToTopic() { - try { - mqttAndroidClient.subscribe(TOPIC, 0, null, new IMqttActionListener() { - @Override - public void onSuccess(IMqttToken asyncActionToken) { - onConnectionSuccess(); - } - - @Override - public void onFailure(IMqttToken asyncActionToken, Throwable exception) { - onConnectionFailure(); - } - }); - } catch (MqttException e) { - Log.e(TAG, "An exception occurred while subscribing." + e.getMessage()); - onConnectionFailure(); - } - } - - /** Unsubscribe from the push notification topic. */ - public void unsubscribeToTopic() { - try { - mqttAndroidClient.unsubscribe(TOPIC); - } catch (MqttException e) { - Log.e(TAG, "An exception occurred while unsubscribing." + e.getMessage()); - onConnectionFailure(); - } - } - - private void onConnectionSuccess() { - - } - - private void onConnectionFailure() { - - } - - private void onConnectionLost() { - - } - - private void onMessageReceived(String message) { - String notificationId = message; // TODO: finalize the form the messages will be received - - Log.d(TAG, "Notification received: " + notificationId); - - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences( - getApplicationContext()); - boolean enabled = preferences.getBoolean("notificationsEnabled", true); - if (!enabled) { - return; - } - - createMastodonAPI(); - - mastodonApi.notification(notificationId).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - NotificationMaker.make(PushNotificationService.this, NOTIFY_ID, - response.body()); - } - } - - @Override - public void onFailure(Call call, Throwable t) {} - }); - } - - /** Disconnect from the MQTT broker. */ - public void disconnect() { - try { - mqttAndroidClient.disconnect(); - } catch (MqttException ex) { - Log.e(TAG, "An exception occurred while disconnecting."); - onDisconnectFailed(); - } - } - - private void onDisconnectFailed() { - - } - - 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 = OkHttpUtils.getCompatibleClientBuilder() - .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()) - .registerTypeAdapter(StringWithEmoji.class, new StringWithEmojiTypeAdapter()) - .create(); - - Retrofit retrofit = new Retrofit.Builder() - .baseUrl("https://" + domain) - .client(okHttpClient) - .addConverterFactory(GsonConverterFactory.create(gson)) - .build(); - - mastodonApi = retrofit.create(MastodonAPI.class); - } -} diff --git a/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java index 40150bb2..259704f8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java @@ -105,8 +105,21 @@ public class PushNotificationClient { // 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; + } + } + } - // Open the MQTT connection. + /** Connect to the MQTT broker. */ + public void connect() { MqttConnectOptions options = new MqttConnectOptions(); options.setAutomaticReconnect(true); options.setCleanSession(false); @@ -140,20 +153,21 @@ public class PushNotificationClient { } }); } catch (MqttException e) { - Log.e(TAG, "An exception occurred while connecting. " + e.getMessage()); + Log.e(TAG, "An exception occurred while connecpting. " + e.getMessage()); onConnectionFailure(); } } - 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; - } - } + 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. */ @@ -215,18 +229,6 @@ public class PushNotificationClient { } } - 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."); - } - private void onMessageReceived(final Context context, String message) { String notificationId = message; // TODO: finalize the form the messages will be received From 5487e8ce7ce8f2712fc17dd8c78682d501cfba00 Mon Sep 17 00:00:00 2001 From: Vavassor Date: Sun, 21 May 2017 22:21:23 -0400 Subject: [PATCH 50/53] First notification received successfully from the wryk/tusky-api prototype! --- .../com/keylesspalace/tusky/BaseActivity.java | 2 +- .../tusky/fragment/NotificationsFragment.java | 3 - .../tusky/util/PushNotificationClient.java | 68 ++----------------- 3 files changed, 6 insertions(+), 67 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index da1656fc..97bbebec 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -174,7 +174,7 @@ public class BaseActivity extends AppCompatActivity { protected void createPushNotificationClient() { // TODO: Switch to ssl:// when TLS support is added. pushNotificationClient = new PushNotificationClient(getApplicationContext(), - "tcp://" + getString(R.string.tusky_api_domain) + ":8000"); + "tcp://" + getString(R.string.tusky_api_domain) + ":1883"); } protected void redirectIfNotLoggedIn() { diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java index 234ece7f..bef04290 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java @@ -233,9 +233,6 @@ public class NotificationsFragment extends SFragment implements } else { adapter.update(notifications); } - for (Notification notification : notifications) { - Log.d(TAG, "id: " + notification.id); - } if (notifications.size() == 0 && adapter.getItemCount() == 1) { adapter.setFooterState(NotificationsAdapter.FooterState.EMPTY); } else if (fromId != null) { diff --git a/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java index 259704f8..1d7741a3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java @@ -2,18 +2,15 @@ package com.keylesspalace.tusky.util; import android.app.NotificationManager; import android.content.Context; -import android.content.SharedPreferences; import android.support.annotation.NonNull; import android.text.Spanned; 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 com.keylesspalace.tusky.network.MastodonAPI; import org.eclipse.paho.android.service.MqttAndroidClient; import org.eclipse.paho.client.mqttv3.DisconnectedBufferOptions; @@ -26,20 +23,9 @@ import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; -import java.io.IOException; -import java.io.InputStream; import java.util.ArrayDeque; import java.util.ArrayList; -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; - import static android.content.Context.NOTIFICATION_SERVICE; public class PushNotificationClient { @@ -67,7 +53,6 @@ public class PushNotificationClient { } private MqttAndroidClient mqttAndroidClient; - private MastodonAPI mastodonApi; private ArrayDeque queuedActions; private ArrayList subscribedTopics; @@ -148,7 +133,8 @@ public class PushNotificationClient { @Override public void onFailure(IMqttToken asyncActionToken, Throwable exception) { - Log.e(TAG, "An exception occurred while connecting. " + exception.getMessage()); + Log.e(TAG, "An exception occurred while connecting. " + exception.getMessage() + + " " + exception.getCause()); onConnectionFailure(); } }); @@ -230,59 +216,15 @@ public class PushNotificationClient { } private void onMessageReceived(final Context context, String message) { - String notificationId = message; // TODO: finalize the form the messages will be received - - Log.v(TAG, "Notification received: " + notificationId); - - createMastodonAPI(context); - - mastodonApi.notification(notificationId).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - NotificationMaker.make(context, NOTIFY_ID, response.body()); - } - } - - @Override - public void onFailure(Call call, Throwable t) {} - }); - } - - private void createMastodonAPI(Context context) { - SharedPreferences preferences = context.getSharedPreferences( - context.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 = OkHttpUtils.getCompatibleClientBuilder() - .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(); + 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); - Retrofit retrofit = new Retrofit.Builder() - .baseUrl("https://" + domain) - .client(okHttpClient) - .addConverterFactory(GsonConverterFactory.create(gson)) - .build(); - - mastodonApi = retrofit.create(MastodonAPI.class); + NotificationMaker.make(context, NOTIFY_ID, notification); } public void clearNotifications(Context context) { From efaf0f50b5ec96ac29cf43cc8c858f6d8ab7b9a1 Mon Sep 17 00:00:00 2001 From: Vavassor Date: Mon, 22 May 2017 02:05:37 -0400 Subject: [PATCH 51/53] Adds TLS to the push notification client (keystore_tusky_api is omitted). --- .../com/keylesspalace/tusky/BaseActivity.java | 15 +++++++-------- .../tusky/util/PushNotificationClient.java | 6 +++--- app/src/main/res/values/donottranslate.xml | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index 97bbebec..c6d27244 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -163,7 +163,7 @@ public class BaseActivity extends AppCompatActivity { protected void createTuskyApi() { Retrofit retrofit = new Retrofit.Builder() - .baseUrl("http://" + getString(R.string.tusky_api_domain) + ":8080") + .baseUrl("https://" + getString(R.string.tusky_api_domain) + ":8080") .client(OkHttpUtils.getCompatibleClient()) .addConverterFactory(GsonConverterFactory.create()) .build(); @@ -172,9 +172,8 @@ public class BaseActivity extends AppCompatActivity { } protected void createPushNotificationClient() { - // TODO: Switch to ssl:// when TLS support is added. pushNotificationClient = new PushNotificationClient(getApplicationContext(), - "tcp://" + getString(R.string.tusky_api_domain) + ":1883"); + "ssl://" + getString(R.string.tusky_api_domain) + ":8883"); } protected void redirectIfNotLoggedIn() { @@ -214,15 +213,15 @@ public class BaseActivity extends AppCompatActivity { retrofit2.Response response) { if (response.isSuccessful()) { pushNotificationClient.subscribeToTopic(getPushNotificationTopic()); - pushNotificationClient.connect(); + pushNotificationClient.connect(BaseActivity.this); } else { - onEnablePushNotificationsFailure(); + onEnablePushNotificationsFailure(response.message()); } } @Override public void onFailure(Call call, Throwable t) { - onEnablePushNotificationsFailure(); + onEnablePushNotificationsFailure(t.getMessage()); } }; String deviceToken = pushNotificationClient.getDeviceToken(); @@ -231,8 +230,8 @@ public class BaseActivity extends AppCompatActivity { .enqueue(callback); } - private void onEnablePushNotificationsFailure() { - Log.e(TAG, "Enabling push notifications failed."); + private void onEnablePushNotificationsFailure(String message) { + Log.e(TAG, "Enabling push notifications failed. " + message); } protected void disablePushNotifications() { diff --git a/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java index 1d7741a3..f3df738f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java @@ -7,6 +7,7 @@ import android.text.Spanned; 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; @@ -23,6 +24,7 @@ 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; @@ -104,12 +106,11 @@ public class PushNotificationClient { } /** Connect to the MQTT broker. */ - public void connect() { + 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 { @@ -117,7 +118,6 @@ public class PushNotificationClient { } finally { IOUtils.closeQuietly(keystore); } - */ mqttAndroidClient.connect(options).setActionCallback(new IMqttActionListener() { @Override public void onSuccess(IMqttToken asyncActionToken) { diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index e6982534..6d61f3f3 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -2,7 +2,7 @@ Tusky https://tusky.keylesspalace.com - tuskyapi.keylesspalace.com + apitusky.keylesspalace.com your_password_here oauth2redirect From dfe9cae90b25d175dbb0ca6b949c7490a6eab833 Mon Sep 17 00:00:00 2001 From: Vavassor Date: Tue, 23 May 2017 19:48:54 -0400 Subject: [PATCH 52/53] pretty much finishes the prototype --- app/src/main/java/com/keylesspalace/tusky/BaseActivity.java | 4 ++-- app/src/main/res/values/donottranslate.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index c6d27244..fa569fa8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -163,7 +163,7 @@ public class BaseActivity extends AppCompatActivity { protected void createTuskyApi() { Retrofit retrofit = new Retrofit.Builder() - .baseUrl("https://" + getString(R.string.tusky_api_domain) + ":8080") + .baseUrl("https://" + getString(R.string.tusky_api_url)) .client(OkHttpUtils.getCompatibleClient()) .addConverterFactory(GsonConverterFactory.create()) .build(); @@ -173,7 +173,7 @@ public class BaseActivity extends AppCompatActivity { protected void createPushNotificationClient() { pushNotificationClient = new PushNotificationClient(getApplicationContext(), - "ssl://" + getString(R.string.tusky_api_domain) + ":8883"); + "ssl://" + getString(R.string.tusky_api_url) + ":8883"); } protected void redirectIfNotLoggedIn() { diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 6d61f3f3..ad2d3b37 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -2,7 +2,7 @@ Tusky https://tusky.keylesspalace.com - apitusky.keylesspalace.com + apitusky.keylesspalace.com your_password_here oauth2redirect From b4e8fe9a78cd54a74ed73c0f26cf817c3e55d2f6 Mon Sep 17 00:00:00 2001 From: Vavassor Date: Tue, 23 May 2017 20:13:49 -0400 Subject: [PATCH 53/53] release 1.1.4-beta.1 --- app/build.gradle | 4 ++-- app/src/main/AndroidManifest.xml | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index cb33758a..6453f7c5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.keylesspalace.tusky" minSdkVersion 15 targetSdkVersion 25 - versionCode 16 - versionName "1.1.3" + versionCode 17 + versionName "1.1.4-beta.1" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary true } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cbe15faa..c30a26ca 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,7 +9,6 @@ -