diff --git a/app/build.gradle b/app/build.gradle index 414114b4..f3ab8750 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -76,6 +76,7 @@ dependencies { implementation "com.google.code.gson:gson:2.8.6" implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' + implementation 'com.squareup.retrofit2:converter-simplexml:2.9.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.preference:preference:1.2.0' implementation "org.conscrypt:conscrypt-android:2.5.2" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b79bd1f0..b736d8d6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,12 +18,14 @@ android:icon="@mipmap/ic_launcher" android:usesCleartextTraffic="true" android:label="@string/app_name" + android:configChanges="orientation|screenSize" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppThemeDark" > diff --git a/app/src/main/java/app/fedilab/android/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/BaseMainActivity.java index 80abbd81..2f733a7c 100644 --- a/app/src/main/java/app/fedilab/android/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/BaseMainActivity.java @@ -55,6 +55,7 @@ import androidx.core.app.ActivityOptionsCompat; import androidx.core.content.ContextCompat; import androidx.core.view.GravityCompat; import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; import androidx.lifecycle.ViewModelProvider; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.navigation.NavController; @@ -746,12 +747,10 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt itemFilter.setTitle(show_filtered); } popup.setOnDismissListener(menu1 -> { - if (binding.viewPager.getAdapter() != null) { - Fragment fragment = getSupportFragmentManager().findFragmentByTag("f" + binding.viewPager.getCurrentItem()); - if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) { - FragmentMastodonTimeline fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment); - fragmentMastodonTimeline.refreshAllAdapters(); - } + Fragment fragment = getSupportFragmentManager().findFragmentByTag("f" + binding.viewPager.getCurrentItem()); + if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) { + FragmentMastodonTimeline fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment); + fragmentMastodonTimeline.refreshAllAdapters(); } }); String finalShow_filtered = show_filtered; @@ -838,8 +837,13 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt } public void refreshFragment() { - if (binding.viewPager.getAdapter() != null) { - binding.viewPager.getAdapter().notifyDataSetChanged(); + Fragment fragment = getSupportFragmentManager().findFragmentByTag("f" + binding.viewPager.getCurrentItem()); + if (fragment instanceof FragmentNotificationContainer) { + FragmentTransaction fragTransaction = getSupportFragmentManager().beginTransaction(); + fragTransaction.detach(fragment).commit(); + FragmentTransaction fragTransaction2 = getSupportFragmentManager().beginTransaction(); + fragTransaction2.attach(fragment); + fragTransaction2.commit(); } } diff --git a/app/src/main/java/app/fedilab/android/activities/MediaActivity.java b/app/src/main/java/app/fedilab/android/activities/MediaActivity.java index 4af12d8d..baeb5a82 100644 --- a/app/src/main/java/app/fedilab/android/activities/MediaActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/MediaActivity.java @@ -125,7 +125,7 @@ public class MediaActivity extends BaseActivity implements OnDownloadInterface { ScreenSlidePagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(MediaActivity.this); binding.mediaViewpager.setAdapter(mPagerAdapter); - + binding.mediaViewpager.setSaveEnabled(false); binding.mediaViewpager.setCurrentItem(mediaPosition - 1); binding.haulerView.setOnDragDismissedListener(dragDirection -> ActivityCompat.finishAfterTransition(MediaActivity.this)); registerReceiver(onDownloadComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); diff --git a/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java b/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java index 3cb3ac0a..ec1f3a9c 100644 --- a/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java @@ -59,12 +59,11 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import androidx.viewpager2.widget.ViewPager2; import com.bumptech.glide.Glide; import com.bumptech.glide.request.target.CustomTarget; import com.bumptech.glide.request.transition.Transition; -import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; import java.util.ArrayList; import java.util.Date; @@ -249,34 +248,28 @@ public class ProfileActivity extends BaseActivity { binding.accountTabLayout.removeAllTabs(); //Tablayout for timelines/following/followers FedilabProfileTLPageAdapter fedilabProfileTLPageAdapter = new FedilabProfileTLPageAdapter(ProfileActivity.this, account); - binding.accountTabLayout.addTab(binding.accountTabLayout.newTab().setText(getString(R.string.status_cnt, Helper.withSuffix(account.statuses_count)))); - binding.accountTabLayout.addTab(binding.accountTabLayout.newTab().setText(getString(R.string.following_cnt, Helper.withSuffix(account.following_count)))); - binding.accountTabLayout.addTab(binding.accountTabLayout.newTab().setText(getString(R.string.followers_cnt, Helper.withSuffix(account.followers_count)))); + binding.accountTabLayout.addTab(binding.accountTabLayout.newTab()); + binding.accountTabLayout.addTab(binding.accountTabLayout.newTab()); + binding.accountTabLayout.addTab(binding.accountTabLayout.newTab()); binding.accountViewpager.setAdapter(fedilabProfileTLPageAdapter); binding.accountViewpager.setOffscreenPageLimit(3); - binding.accountViewpager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - super.onPageScrolled(position, positionOffset, positionOffsetPixels); - binding.accountTabLayout.selectTab(binding.accountTabLayout.getTabAt(position)); - } - }); - binding.accountTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { - @Override - public void onTabSelected(TabLayout.Tab tab) { - binding.accountViewpager.setCurrentItem(tab.getPosition()); - } - - @Override - public void onTabUnselected(TabLayout.Tab tab) { - - } - - @Override - public void onTabReselected(TabLayout.Tab tab) { - } - }); + new TabLayoutMediator(binding.accountTabLayout, binding.accountViewpager, + (tab, position) -> { + binding.accountViewpager.setCurrentItem(tab.getPosition(), true); + switch (position) { + case 0: + tab.setText(getString(R.string.status_cnt, Helper.withSuffix(account.statuses_count))); + break; + case 1: + tab.setText(getString(R.string.following_cnt, Helper.withSuffix(account.following_count))); + break; + case 2: + tab.setText(getString(R.string.followers_cnt, Helper.withSuffix(account.followers_count))); + break; + } + } + ).attach(); binding.accountTabLayout.setTabTextColors(ThemeHelper.getAttColor(ProfileActivity.this, R.attr.mTextColor), ContextCompat.getColor(ProfileActivity.this, R.color.cyanea_accent_dark_reference)); binding.accountTabLayout.setTabIconTint(ThemeHelper.getColorStateList(ProfileActivity.this)); boolean disableGif = sharedpreferences.getBoolean(getString(R.string.SET_DISABLE_GIF), false); diff --git a/app/src/main/java/app/fedilab/android/activities/ReorderTimelinesActivity.java b/app/src/main/java/app/fedilab/android/activities/ReorderTimelinesActivity.java index cde71cdf..90a6c0cc 100644 --- a/app/src/main/java/app/fedilab/android/activities/ReorderTimelinesActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/ReorderTimelinesActivity.java @@ -19,6 +19,7 @@ import static app.fedilab.android.helper.PinnedTimelineHelper.sortMenuItem; import static app.fedilab.android.helper.PinnedTimelineHelper.sortPositionAsc; import android.content.Intent; +import android.content.SharedPreferences; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.os.Handler; @@ -37,6 +38,7 @@ import androidx.appcompat.app.AlertDialog; import androidx.core.content.ContextCompat; import androidx.lifecycle.ViewModelProvider; import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -69,8 +71,10 @@ import app.fedilab.android.viewmodel.mastodon.ReorderVM; import es.dmoral.toasty.Toasty; import okhttp3.Call; import okhttp3.Callback; +import okhttp3.FormBody; import okhttp3.OkHttpClient; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; @@ -87,7 +91,7 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra private ActivityReorderTabsBinding binding; private boolean changes; private boolean bottomChanges; - + private boolean update; public void setChanges(boolean changes) { this.changes = changes; } @@ -112,10 +116,12 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra bottomChanges = false; ReorderVM reorderVM = new ViewModelProvider(ReorderTimelinesActivity.this).get(ReorderVM.class); reorderVM.getPinned().observe(ReorderTimelinesActivity.this, _pinned -> { + update = true; this.pinned = _pinned; if (this.pinned == null) { this.pinned = new Pinned(); this.pinned.pinnedTimelines = new ArrayList<>(); + update = false; } sortPositionAsc(this.pinned.pinnedTimelines); reorderTabAdapter = new ReorderTabAdapter(this.pinned, ReorderTimelinesActivity.this, ReorderTimelinesActivity.this); @@ -153,7 +159,6 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra } else if (item.getItemId() == R.id.action_add_timeline) { addInstance(); } - return super.onOptionsItemSelected(item); } @@ -168,11 +173,17 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(ReorderTimelinesActivity.this, Helper.dialogStyle()); PopupSearchInstanceBinding popupSearchInstanceBinding = PopupSearchInstanceBinding.inflate(getLayoutInflater()); dialogBuilder.setView(popupSearchInstanceBinding.getRoot()); + TextWatcher textWatcher = autoComplete(popupSearchInstanceBinding); + popupSearchInstanceBinding.searchInstance.addTextChangedListener(textWatcher); + popupSearchInstanceBinding.setAttachmentGroup.setOnCheckedChangeListener((group, checkedId) -> { if (checkedId == R.id.twitter_accounts) { popupSearchInstanceBinding.searchInstance.setHint(R.string.list_of_twitter_accounts); + popupSearchInstanceBinding.searchInstance.removeTextChangedListener(textWatcher); } else { popupSearchInstanceBinding.searchInstance.setHint(R.string.instance); + popupSearchInstanceBinding.searchInstance.removeTextChangedListener(textWatcher); + popupSearchInstanceBinding.searchInstance.addTextChangedListener(textWatcher); } }); popupSearchInstanceBinding.searchInstance.setFilters(new InputFilter[]{new InputFilter.LengthFilter(60)}); @@ -180,16 +191,24 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra String instanceName = popupSearchInstanceBinding.searchInstance.getText().toString().trim().replace("@", ""); new Thread(() -> { String url = null; - if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.mastodon_instance) + boolean getCall = true; + RequestBody formBody = new FormBody.Builder() + .build(); + if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.mastodon_instance) { url = "https://" + instanceName + "/api/v1/timelines/public?local=true"; - else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.peertube_instance) + } else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.peertube_instance) { url = "https://" + instanceName + "/api/v1/videos/"; - else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.pixelfed_instance) { + } else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.pixelfed_instance) { url = "https://" + instanceName + "/api/v1/timelines/public"; } else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.misskey_instance) { url = "https://" + instanceName + "/api/notes/local-timeline"; + getCall = false; } else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.gnu_instance) { url = "https://" + instanceName + "/api/statuses/public_timeline.json"; + } else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.twitter_accounts) { + SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(ReorderTimelinesActivity.this); + String nitterHost = sharedpreferences.getString(getString(R.string.SET_NITTER_HOST), getString(R.string.DEFAULT_NITTER_HOST)).toLowerCase(); + url = "https://" + nitterHost + "/" + instanceName.replaceAll("\\s", "") + "/rss"; } OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) @@ -198,9 +217,16 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra .readTimeout(10, TimeUnit.SECONDS).build(); Request request; if (url != null) { - request = new Request.Builder() - .url(url) - .build(); + if (getCall) { + request = new Request.Builder() + .url(url) + .build(); + } else { + request = new Request.Builder() + .url(url) + .post(formBody) + .build(); + } client.newCall(request).enqueue(new Callback() { @Override public void onFailure(@NonNull Call call, @NonNull IOException e) { @@ -236,13 +262,26 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra pinnedTimeline.type = Timeline.TimeLineEnum.REMOTE; pinnedTimeline.position = pinned.pinnedTimelines.size(); pinned.pinnedTimelines.add(pinnedTimeline); - try { - new Pinned(ReorderTimelinesActivity.this).updatePinned(pinned); - changes = true; - reorderTabAdapter.notifyItemInserted(pinned.pinnedTimelines.size()); - } catch (DBException e) { - e.printStackTrace(); + + if (update) { + try { + new Pinned(ReorderTimelinesActivity.this).updatePinned(pinned); + } catch (DBException e) { + e.printStackTrace(); + } + } else { + try { + new Pinned(ReorderTimelinesActivity.this).insertPinned(pinned); + } catch (DBException e) { + e.printStackTrace(); + } } + reorderTabAdapter.notifyItemInserted(pinned.pinnedTimelines.size()); + Bundle b = new Bundle(); + b.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true); + Intent intentBD = new Intent(Helper.BROADCAST_DATA); + intentBD.putExtras(b); + LocalBroadcastManager.getInstance(ReorderTimelinesActivity.this).sendBroadcast(intentBD); }); } else { runOnUiThread(() -> Toasty.warning(ReorderTimelinesActivity.this, getString(R.string.toast_instance_unavailable), Toast.LENGTH_LONG).show()); @@ -267,7 +306,11 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra popupSearchInstanceBinding.searchInstance.setOnItemClickListener((parent, view1, position, id) -> oldSearch = parent.getItemAtPosition(position).toString().trim()); - popupSearchInstanceBinding.searchInstance.addTextChangedListener(new TextWatcher() { + + } + + private TextWatcher autoComplete(PopupSearchInstanceBinding popupSearchInstanceBinding) { + return new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @@ -314,8 +357,7 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra } } } - }); - + }; } @Override diff --git a/app/src/main/java/app/fedilab/android/activities/ScheduledActivity.java b/app/src/main/java/app/fedilab/android/activities/ScheduledActivity.java index 4ea741e5..da261e98 100644 --- a/app/src/main/java/app/fedilab/android/activities/ScheduledActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/ScheduledActivity.java @@ -24,7 +24,7 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.core.content.ContextCompat; -import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; import app.fedilab.android.R; import app.fedilab.android.databinding.ActivityScheduledBinding; @@ -56,31 +56,31 @@ public class ScheduledActivity extends BaseActivity { MastodonHelper.loadPPMastodon(binding.profilePicture, currentAccount.mastodon_account); binding.title.setText(R.string.scheduled); - binding.scheduleTablayout.addTab(binding.scheduleTablayout.newTab().setText(getString(R.string.toots_server))); - binding.scheduleTablayout.addTab(binding.scheduleTablayout.newTab().setText(getString(R.string.toots_client))); - binding.scheduleTablayout.addTab(binding.scheduleTablayout.newTab().setText(getString(R.string.reblog))); + binding.scheduleTablayout.addTab(binding.scheduleTablayout.newTab()); + binding.scheduleTablayout.addTab(binding.scheduleTablayout.newTab()); + binding.scheduleTablayout.addTab(binding.scheduleTablayout.newTab()); binding.scheduleViewpager.setAdapter(new FedilabScheduledPageAdapter(ScheduledActivity.this)); binding.scheduleViewpager.setOffscreenPageLimit(3); binding.scheduleTablayout.setTabTextColors(ThemeHelper.getAttColor(ScheduledActivity.this, R.attr.mTextColor), ContextCompat.getColor(ScheduledActivity.this, R.color.cyanea_accent_dark_reference)); binding.scheduleTablayout.setTabIconTint(ThemeHelper.getColorStateList(ScheduledActivity.this)); - binding.scheduleTablayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { - @Override - public void onTabSelected(TabLayout.Tab tab) { - binding.scheduleViewpager.setCurrentItem(tab.getPosition()); - } - - @Override - public void onTabUnselected(TabLayout.Tab tab) { - - } - - @Override - public void onTabReselected(TabLayout.Tab tab) { - - } - }); + new TabLayoutMediator(binding.scheduleTablayout, binding.scheduleViewpager, + (tab, position) -> { + binding.scheduleViewpager.setCurrentItem(tab.getPosition(), true); + switch (position) { + case 0: + tab.setText(getString(R.string.toots_server)); + break; + case 1: + tab.setText(getString(R.string.toots_client)); + break; + case 2: + tab.setText(getString(R.string.reblog)); + break; + } + } + ).attach(); } @Override diff --git a/app/src/main/java/app/fedilab/android/activities/SearchResultTabActivity.java b/app/src/main/java/app/fedilab/android/activities/SearchResultTabActivity.java index c09fac97..04195b35 100644 --- a/app/src/main/java/app/fedilab/android/activities/SearchResultTabActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/SearchResultTabActivity.java @@ -30,9 +30,9 @@ import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.viewpager2.adapter.FragmentStateAdapter; -import androidx.viewpager2.widget.ViewPager2; import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; import org.jetbrains.annotations.NotNull; @@ -75,13 +75,35 @@ public class SearchResultTabActivity extends BaseActivity { getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary))); } setTitle(search); - binding.searchTabLayout.addTab(binding.searchTabLayout.newTab().setText(getString(R.string.tags))); - binding.searchTabLayout.addTab(binding.searchTabLayout.newTab().setText(getString(R.string.accounts))); - binding.searchTabLayout.addTab(binding.searchTabLayout.newTab().setText(getString(R.string.toots))); - binding.searchTabLayout.addTab(binding.searchTabLayout.newTab().setText(getString(R.string.action_cache))); + binding.searchTabLayout.addTab(binding.searchTabLayout.newTab()); + binding.searchTabLayout.addTab(binding.searchTabLayout.newTab()); + binding.searchTabLayout.addTab(binding.searchTabLayout.newTab()); + binding.searchTabLayout.addTab(binding.searchTabLayout.newTab()); binding.searchTabLayout.setTabTextColors(ThemeHelper.getAttColor(SearchResultTabActivity.this, R.attr.mTextColor), ContextCompat.getColor(SearchResultTabActivity.this, R.color.cyanea_accent_dark_reference)); binding.searchTabLayout.setTabIconTint(ThemeHelper.getColorStateList(SearchResultTabActivity.this)); - + ScreenSlidePagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(SearchResultTabActivity.this); + binding.searchViewpager.setAdapter(mPagerAdapter); + binding.searchViewpager.setSaveEnabled(false); + binding.searchViewpager.setOffscreenPageLimit(3); + new TabLayoutMediator(binding.searchTabLayout, binding.searchViewpager, + (tab, position) -> { + binding.searchViewpager.setCurrentItem(tab.getPosition(), true); + switch (position) { + case 0: + tab.setText(getString(R.string.tags)); + break; + case 1: + tab.setText(getString(R.string.accounts)); + break; + case 2: + tab.setText(getString(R.string.toots)); + break; + case 3: + tab.setText(getString(R.string.action_cache)); + break; + } + } + ).attach(); binding.searchTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override public void onTabSelected(TabLayout.Tab tab) { @@ -95,19 +117,16 @@ public class SearchResultTabActivity extends BaseActivity { @Override public void onTabReselected(TabLayout.Tab tab) { - Fragment fragment; - if (binding.searchViewpager.getAdapter() != null) { - fragment = (Fragment) getSupportFragmentManager().findFragmentByTag("f" + binding.searchViewpager.getCurrentItem()); - if (fragment instanceof FragmentMastodonAccount) { - FragmentMastodonAccount fragmentMastodonAccount = ((FragmentMastodonAccount) fragment); - fragmentMastodonAccount.scrollToTop(); - } else if (fragment instanceof FragmentMastodonTimeline) { - FragmentMastodonTimeline fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment); - fragmentMastodonTimeline.scrollToTop(); - } else if (fragment instanceof FragmentMastodonTag) { - FragmentMastodonTag fragmentMastodonTag = ((FragmentMastodonTag) fragment); - fragmentMastodonTag.scrollToTop(); - } + Fragment fragment = getSupportFragmentManager().findFragmentByTag("f" + binding.searchViewpager.getCurrentItem()); + if (fragment instanceof FragmentMastodonAccount) { + FragmentMastodonAccount fragmentMastodonAccount = ((FragmentMastodonAccount) fragment); + fragmentMastodonAccount.scrollToTop(); + } else if (fragment instanceof FragmentMastodonTimeline) { + FragmentMastodonTimeline fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment); + fragmentMastodonTimeline.scrollToTop(); + } else if (fragment instanceof FragmentMastodonTag) { + FragmentMastodonTag fragmentMastodonTag = ((FragmentMastodonTag) fragment); + fragmentMastodonTag.scrollToTop(); } } }); @@ -122,8 +141,6 @@ public class SearchResultTabActivity extends BaseActivity { SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView(); searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); searchView.setIconifiedByDefault(false); - - searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { @@ -156,29 +173,7 @@ public class SearchResultTabActivity extends BaseActivity { }); - ScreenSlidePagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(SearchResultTabActivity.this); - binding.searchViewpager.setAdapter(mPagerAdapter); - binding.searchViewpager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - super.onPageScrolled(position, positionOffset, positionOffsetPixels); - binding.searchTabLayout.selectTab(binding.searchTabLayout.getTabAt(position)); - } - - @Override - public void onPageSelected(int position) { - super.onPageSelected(position); - TabLayout.Tab tab = binding.searchTabLayout.getTabAt(position); - if (tab != null) - tab.select(); - } - - @Override - public void onPageScrollStateChanged(int state) { - super.onPageScrollStateChanged(state); - } - }); return true; } diff --git a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonTimelinesService.java b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonTimelinesService.java index 01dfb83f..ffe82566 100644 --- a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonTimelinesService.java +++ b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonTimelinesService.java @@ -21,12 +21,17 @@ import app.fedilab.android.client.entities.api.Conversation; import app.fedilab.android.client.entities.api.Marker; import app.fedilab.android.client.entities.api.MastodonList; import app.fedilab.android.client.entities.api.Status; +import app.fedilab.android.client.entities.misskey.MisskeyNote; +import app.fedilab.android.client.entities.nitter.Nitter; +import app.fedilab.android.client.entities.peertube.PeertubeVideo; import retrofit2.Call; +import retrofit2.http.Body; import retrofit2.http.DELETE; import retrofit2.http.Field; import retrofit2.http.FormUrlEncoded; import retrofit2.http.GET; import retrofit2.http.Header; +import retrofit2.http.Headers; import retrofit2.http.POST; import retrofit2.http.PUT; import retrofit2.http.Path; @@ -191,4 +196,48 @@ public interface MastodonTimelinesService { @Field("home[last_read_id]") String home_last_read_id, @Field("notifications[last_read_id]") String notifications_last_read_id ); + + + @Headers({"Accept: application/json"}) + @POST("api/notes") + Call> getMisskey(@Body MisskeyNote.MisskeyParams params); + + + //Public timelines for Misskey + @FormUrlEncoded + @POST("api/notes") + Call> getMisskey( + @Field("local") boolean local, + @Field("file") boolean file, + @Field("poll") boolean poll, + @Field("remote") boolean remote, + @Field("reply") boolean reply, + @Field("untilId") String max_id, + @Field("since_id") String since_id, + @Field("limit") Integer limit + ); + + @GET("api/v1/videos") + Call getPeertube( + @Query("start") String start, + @Query("filter") String filter, + @Query("sort") String sort, + @Query("count") int count + ); + + @GET("{names}/rss") + Call getNitter( + @Path("names") String id, + @Query("max_position") String max_position + ); + + @GET("{account}/rss") + Call getNitterAccount( + @Path("account") String account + ); + + @GET("api/v1/videos/{id}") + Call getPeertubeVideo( + @Path("id") String id + ); } diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/Attachment.java b/app/src/main/java/app/fedilab/android/client/entities/api/Attachment.java index 5f11e2e1..2d06c314 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/api/Attachment.java +++ b/app/src/main/java/app/fedilab/android/client/entities/api/Attachment.java @@ -44,4 +44,6 @@ public class Attachment implements Serializable { @SerializedName("local_path") public String local_path; + public String peertubeHost = null; + public String peertubeId = null; } diff --git a/app/src/main/java/app/fedilab/android/client/entities/app/Account.java b/app/src/main/java/app/fedilab/android/client/entities/app/Account.java index 341e6e93..1be6458b 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/app/Account.java +++ b/app/src/main/java/app/fedilab/android/client/entities/app/Account.java @@ -277,7 +277,7 @@ public class Account extends BaseAccount implements Serializable { throw new DBException("db is null. Wrong initialization."); } try { - Cursor c = db.query(Sqlite.TABLE_USER_ACCOUNT, null, Sqlite.COL_API + " = 'MASTODON'", null, null, null, null, null); + Cursor c = db.query(Sqlite.TABLE_USER_ACCOUNT, null, null, null, null, null, null, null); return cursorToListUser(c); } catch (Exception e) { return null; diff --git a/app/src/main/java/app/fedilab/android/client/entities/misskey/MisskeyNote.java b/app/src/main/java/app/fedilab/android/client/entities/misskey/MisskeyNote.java new file mode 100644 index 00000000..43832531 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/client/entities/misskey/MisskeyNote.java @@ -0,0 +1,150 @@ +package app.fedilab.android.client.entities.misskey; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * 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. + * + * Fedilab 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 Fedilab; if not, + * see . */ + + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import app.fedilab.android.client.entities.api.Account; +import app.fedilab.android.client.entities.api.Attachment; +import app.fedilab.android.client.entities.api.Status; + + +@SuppressWarnings("ALL") +public class MisskeyNote implements Serializable { + + @SerializedName("id") + public String id; + @SerializedName("createdAt") + public Date createdAt; + @SerializedName("replyId") + public String replyId; + @SerializedName("cw") + public String cw; + @SerializedName("text") + public String text; + @SerializedName("url") + public String url; + @SerializedName("uri") + public String uri; + @SerializedName("visibility") + public String visibility; + @SerializedName("repliesCount") + public int repliesCount; + @SerializedName("user") + public MisskeyUser user; + @SerializedName("files") + public List files; + @SerializedName("emojis") + public List emojis; + + public static Status convert(MisskeyNote misskeyNote) { + Status status = new Status(); + status.id = misskeyNote.id; + status.in_reply_to_id = misskeyNote.replyId; + status.content = misskeyNote.text != null ? misskeyNote.text : ""; + status.text = misskeyNote.text != null ? misskeyNote.text : ""; + status.spoiler_text = misskeyNote.cw; + status.visibility = misskeyNote.visibility; + status.created_at = misskeyNote.createdAt; + status.uri = misskeyNote.uri; + status.url = misskeyNote.url; + + Account account = new Account(); + account.id = misskeyNote.user.id; + account.acct = misskeyNote.user.username; + account.username = misskeyNote.user.username; + account.display_name = misskeyNote.user.name; + account.avatar = misskeyNote.user.avatarUrl; + account.avatar_static = misskeyNote.user.avatarUrl; + status.account = account; + + if (misskeyNote.files != null && misskeyNote.files.size() > 0) { + List attachmentList = new ArrayList<>(); + for (MisskeyFile misskeyFile : misskeyNote.files) { + Attachment attachment = new Attachment(); + attachment.type = misskeyFile.type; + attachment.description = misskeyFile.comment; + attachment.url = misskeyFile.url; + attachment.preview_url = misskeyFile.thumbnailUrl; + if (misskeyFile.isSensitive) { + status.sensitive = true; + } + attachmentList.add(attachment); + } + status.media_attachments = attachmentList; + } + + return status; + } + + public static class MisskeyUser implements Serializable { + @SerializedName("id") + public String id; + @SerializedName("name") + public String name; + @SerializedName("username") + public String username; + @SerializedName("avatarUrl") + public String avatarUrl; + @SerializedName("emojis") + public List emojis; + } + + public static class MisskeyFile implements Serializable { + @SerializedName("id") + public String id; + @SerializedName("comment") + public String comment; + @SerializedName("isSensitive") + public boolean isSensitive; + @SerializedName("thumbnailUrl") + public String thumbnailUrl; + @SerializedName("url") + public String url; + @SerializedName("type") + public String type; + } + + public static class MisskeyEmoji implements Serializable { + @SerializedName("name") + public String name; + @SerializedName("comment") + public String url; + } + + public static class MisskeyParams implements Serializable { + @SerializedName("local") + public boolean local = true; + @SerializedName("file") + public boolean file = false; + @SerializedName("poll") + public boolean poll = false; + @SerializedName("remote") + public boolean remote = false; + @SerializedName("reply") + public boolean reply = false; + @SerializedName("untilId") + public String untilId; + @SerializedName("limit") + public int limit; + } + +} diff --git a/app/src/main/java/app/fedilab/android/client/entities/nitter/Nitter.java b/app/src/main/java/app/fedilab/android/client/entities/nitter/Nitter.java new file mode 100644 index 00000000..3338d7a8 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/client/entities/nitter/Nitter.java @@ -0,0 +1,170 @@ +package app.fedilab.android.client.entities.nitter; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * 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. + * + * Fedilab 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 Fedilab; if not, + * see . */ + +import android.content.Context; + +import androidx.annotation.NonNull; + +import org.simpleframework.xml.Element; +import org.simpleframework.xml.ElementList; +import org.simpleframework.xml.Namespace; +import org.simpleframework.xml.Path; +import org.simpleframework.xml.Root; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import app.fedilab.android.client.endpoints.MastodonTimelinesService; +import app.fedilab.android.client.entities.api.Attachment; +import app.fedilab.android.client.entities.api.Status; +import app.fedilab.android.helper.Helper; +import okhttp3.OkHttpClient; +import retrofit2.Call; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.simplexml.SimpleXmlConverterFactory; + +@Root(name = "rss", strict = false) +public class Nitter implements Serializable { + + + public static HashMap accounts = new HashMap<>(); + + @Element(name = "title") + @Path("channel") + public String title; + @Element(name = "image") + @Path("channel") + public Image image; + @ElementList(name = "item", inline = true) + @Path("channel") + public List mFeedItems; + + public static MastodonTimelinesService initInstanceXMLOnly(Context context, String instance) { + + OkHttpClient okHttpClient = new OkHttpClient.Builder() + .readTimeout(60, TimeUnit.SECONDS) + .connectTimeout(60, TimeUnit.SECONDS) + .callTimeout(60, TimeUnit.SECONDS) + .proxy(Helper.getProxy(context)) + .build(); + Retrofit retrofit = new Retrofit.Builder() + .baseUrl("https://" + instance) + .addConverterFactory(SimpleXmlConverterFactory.create()) + .client(okHttpClient) + .build(); + return retrofit.create(MastodonTimelinesService.class); + } + + public static Status convert(Context context, String instance, FeedItem feedItem) { + Status status = new Status(); + status.id = feedItem.pubDate; + status.content = feedItem.description; + status.text = feedItem.title; + status.visibility = "public"; + status.created_at = Helper.stringToDateWithFormat(context, feedItem.pubDate, "EEE, dd MMM yyyy HH:mm:ss zzz"); + status.uri = feedItem.guid; + status.url = feedItem.link; + if (!accounts.containsValue(feedItem.creator)) { + MastodonTimelinesService mastodonTimelinesService = initInstanceXMLOnly(context, instance); + Call accountCall = mastodonTimelinesService.getNitterAccount(feedItem.creator.replace("@", "")); + if (accountCall != null) { + try { + Response publicTlResponse = accountCall.execute(); + if (publicTlResponse.isSuccessful()) { + Nitter nitterAccount = publicTlResponse.body(); + accounts.put(feedItem.creator, nitterAccount); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + Nitter nitterAccount = accounts.get(feedItem.creator); + if (nitterAccount != null) { + app.fedilab.android.client.entities.api.Account account = new app.fedilab.android.client.entities.api.Account(); + String[] names = nitterAccount.image.title.split("/"); + account.id = feedItem.guid; + account.acct = names[1].replace("@", ""); + account.username = names[1].replace("@", ""); + account.display_name = names[0]; + account.avatar = nitterAccount.image.url; + account.avatar_static = nitterAccount.image.url; + account.url = nitterAccount.image.link; + status.account = account; + } + + if (feedItem.description != null) { + Pattern imgPattern = Pattern.compile("]*src=\"([^\"]+)\"[^>]*>"); + Matcher matcher = imgPattern.matcher(feedItem.description); + String description = feedItem.description; + ArrayList attachmentList = new ArrayList<>(); + while (matcher.find()) { + description = description.replaceAll(Pattern.quote(matcher.group()), ""); + Attachment attachment = new Attachment(); + attachment.type = "image"; + attachment.url = matcher.group(1); + attachment.preview_url = matcher.group(1); + attachment.id = matcher.group(1); + attachmentList.add(attachment); + } + status.media_attachments = attachmentList; + } + return status; + } + + @Root(name = "image", strict = false) + public static class Image implements Serializable { + @Element(name = "title") + public String title; + @Element(name = "url") + public String url; + @Element(name = "link") + public String link; + } + + @Root(name = "item", strict = false) + public static class FeedItem implements Serializable { + @Namespace(prefix = "dc") + @Element(name = "creator", required = false) + public String creator; + @Element(name = "title", required = false) + public String title; + @Element(name = "description", required = false) + public String description; + @Element(name = "pubDate", required = false) + public String pubDate; + @Element(name = "guid", required = false) + public String guid; + @Element(name = "link", required = false) + public String link; + + @NonNull + @Override + public String toString() { + return "creator: " + creator + "\r" + "title: " + title + "\r" + "description: " + + description + "\r" + "pubDate: " + pubDate + "\r" + + "guid: " + guid + "\r" + "link: " + link; + } + } + + +} diff --git a/app/src/main/java/app/fedilab/android/client/entities/peertube/PeertubeVideo.java b/app/src/main/java/app/fedilab/android/client/entities/peertube/PeertubeVideo.java new file mode 100644 index 00000000..7f48a99a --- /dev/null +++ b/app/src/main/java/app/fedilab/android/client/entities/peertube/PeertubeVideo.java @@ -0,0 +1,211 @@ +package app.fedilab.android.client.entities.peertube; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * 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. + * + * Fedilab 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 Fedilab; if not, + * see . */ + + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import app.fedilab.android.client.entities.api.Account; +import app.fedilab.android.client.entities.api.Attachment; +import app.fedilab.android.client.entities.api.Status; + +@SuppressWarnings("ALL") +public class PeertubeVideo implements Serializable { + + @SerializedName("total") + public int total; + @SerializedName("data") + public List