package app.fedilab.android.mastodon.activities; /* 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.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.widget.ArrayAdapter; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.lifecycle.ViewModelProvider; import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import java.io.IOException; import java.util.ArrayList; import java.util.concurrent.TimeUnit; import app.fedilab.android.BuildConfig; import app.fedilab.android.R; import app.fedilab.android.activities.MainActivity; import app.fedilab.android.databinding.ActivityReorderTabsBinding; import app.fedilab.android.databinding.PopupSearchInstanceBinding; import app.fedilab.android.mastodon.client.entities.app.BottomMenu; import app.fedilab.android.mastodon.client.entities.app.InstanceSocial; import app.fedilab.android.mastodon.client.entities.app.Pinned; import app.fedilab.android.mastodon.client.entities.app.PinnedTimeline; import app.fedilab.android.mastodon.client.entities.app.RemoteInstance; import app.fedilab.android.mastodon.client.entities.app.Timeline; import app.fedilab.android.mastodon.exception.DBException; import app.fedilab.android.mastodon.helper.Helper; import app.fedilab.android.mastodon.helper.PinnedTimelineHelper; import app.fedilab.android.mastodon.helper.itemtouchhelper.OnStartDragListener; import app.fedilab.android.mastodon.helper.itemtouchhelper.SimpleItemTouchHelperCallback; import app.fedilab.android.mastodon.ui.drawer.ReorderBottomMenuAdapter; import app.fedilab.android.mastodon.ui.drawer.ReorderTabAdapter; import app.fedilab.android.mastodon.viewmodel.mastodon.InstanceSocialVM; import app.fedilab.android.mastodon.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; public class ReorderTimelinesActivity extends BaseBarActivity implements OnStartDragListener { private ItemTouchHelper touchHelper; private ReorderTabAdapter reorderTabAdapter; private ReorderBottomMenuAdapter reorderBottomMenuAdapter; private boolean searchInstanceRunning; private String oldSearch; private Pinned pinned; private BottomMenu bottomMenu; private ActivityReorderTabsBinding binding; private boolean changes; private boolean bottomChanges; private boolean update; public void setChanges(boolean changes) { this.changes = changes; } public void setBottomChanges(boolean bottomChanges) { this.bottomChanges = bottomChanges; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = ActivityReorderTabsBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); searchInstanceRunning = false; if (getSupportActionBar() != null) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); } SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(ReorderTimelinesActivity.this); boolean singleBar = sharedpreferences.getBoolean(getString(R.string.SET_USE_SINGLE_TOPBAR), false); if (singleBar) { binding.titleBottom.setVisibility(View.GONE); binding.lvReorderBottom.setVisibility(View.GONE); binding.titleTop.setVisibility(View.GONE); } changes = false; 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; } PinnedTimelineHelper.sortPositionAsc(this.pinned.pinnedTimelines); reorderTabAdapter = new ReorderTabAdapter(this.pinned, ReorderTimelinesActivity.this); ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(reorderTabAdapter); touchHelper = new ItemTouchHelper(callback); touchHelper.attachToRecyclerView(binding.lvReorderTabs); binding.lvReorderTabs.setAdapter(reorderTabAdapter); LinearLayoutManager mLayoutManager = new LinearLayoutManager(ReorderTimelinesActivity.this); binding.lvReorderTabs.setLayoutManager(mLayoutManager); }); reorderVM.getBottomMenu().observe(ReorderTimelinesActivity.this, _bottomMenu -> { this.bottomMenu = _bottomMenu; if (_bottomMenu == null) { this.bottomMenu = new BottomMenu(getApplicationContext()).defaultBottomMenu(); this.bottomMenu.bottom_menu = new ArrayList<>(); } PinnedTimelineHelper.sortMenuItem(this.bottomMenu.bottom_menu); reorderBottomMenuAdapter = new ReorderBottomMenuAdapter(this.bottomMenu, ReorderTimelinesActivity.this); ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(reorderBottomMenuAdapter); touchHelper = new ItemTouchHelper(callback); touchHelper.attachToRecyclerView(binding.lvReorderBottom); binding.lvReorderBottom.setAdapter(reorderBottomMenuAdapter); LinearLayoutManager mLayoutManager = new LinearLayoutManager(ReorderTimelinesActivity.this); binding.lvReorderBottom.setLayoutManager(mLayoutManager); }); } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { finish(); return true; } else if (item.getItemId() == R.id.action_add_timeline) { addInstance(); } return super.onOptionsItemSelected(item); } @Override public boolean onCreateOptionsMenu(@NonNull Menu menu) { getMenuInflater().inflate(R.menu.menu_reorder, menu); return super.onCreateOptionsMenu(menu); } private void addInstance() { AlertDialog.Builder dialogBuilder = new MaterialAlertDialogBuilder(ReorderTimelinesActivity.this); 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); } }); dialogBuilder.setPositiveButton(R.string.validate, (dialog, id) -> { String instanceName = popupSearchInstanceBinding.searchInstance.getText().toString().trim().replace("@", ""); new Thread(() -> { String url = null; 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) { url = "https://" + instanceName + "/api/v1/videos/"; } 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.lemmy_instance) { url = "https://" + instanceName + "/api/v3/post/list"; } 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("[ ]+", ",").replaceAll("\\s", "") + "/with_replies/rss"; } OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .proxy(Helper.getProxy(getApplication().getApplicationContext())) .readTimeout(10, TimeUnit.SECONDS).build(); Request request; if (url != null) { 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) { e.printStackTrace(); runOnUiThread(() -> Toasty.warning(ReorderTimelinesActivity.this, getString(R.string.toast_instance_unavailable), Toast.LENGTH_LONG).show()); } @Override public void onResponse(@NonNull Call call, @NonNull final Response response) { if (response.isSuccessful()) { runOnUiThread(() -> { dialog.dismiss(); RemoteInstance.InstanceType instanceType = null; if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.mastodon_instance) { instanceType = RemoteInstance.InstanceType.MASTODON; } else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.peertube_instance) { instanceType = RemoteInstance.InstanceType.PEERTUBE; } else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.pixelfed_instance) { instanceType = RemoteInstance.InstanceType.PIXELFED; } else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.misskey_instance) { instanceType = RemoteInstance.InstanceType.MISSKEY; } else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.lemmy_instance) { instanceType = RemoteInstance.InstanceType.LEMMY; } else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.gnu_instance) { instanceType = RemoteInstance.InstanceType.GNU; } else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.twitter_accounts) { instanceType = RemoteInstance.InstanceType.NITTER; } RemoteInstance remoteInstance = new RemoteInstance(); remoteInstance.type = instanceType; remoteInstance.host = instanceName; PinnedTimeline pinnedTimeline = new PinnedTimeline(); pinnedTimeline.remoteInstance = remoteInstance; pinnedTimeline.displayed = true; pinnedTimeline.type = Timeline.TimeLineEnum.REMOTE; pinnedTimeline.position = pinned.pinnedTimelines.size(); pinned.pinnedTimelines.add(pinnedTimeline); if (pinned.user_id == null || pinned.instance == null) { pinned.user_id = MainActivity.currentUserID; pinned.instance = MainActivity.currentInstance; } 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); intentBD.setPackage(BuildConfig.APPLICATION_ID); sendBroadcast(intentBD); }); } else { runOnUiThread(() -> Toasty.warning(ReorderTimelinesActivity.this, getString(R.string.toast_instance_unavailable), Toast.LENGTH_LONG).show()); } } }); } else { runOnUiThread(() -> Toasty.warning(ReorderTimelinesActivity.this, getString(R.string.toast_instance_unavailable), Toast.LENGTH_LONG).show()); } }).start(); }); AlertDialog alertDialog = dialogBuilder.create(); alertDialog.setOnDismissListener(dialogInterface -> { //Hide keyboard InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); assert imm != null; imm.hideSoftInputFromWindow(popupSearchInstanceBinding.searchInstance.getWindowToken(), 0); }); if (alertDialog.getWindow() != null) alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); alertDialog.show(); popupSearchInstanceBinding.searchInstance.setOnItemClickListener((parent, view1, position, id) -> oldSearch = parent.getItemAtPosition(position).toString().trim()); } private TextWatcher autoComplete(PopupSearchInstanceBinding popupSearchInstanceBinding) { return new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { if (s.length() > 2 && !searchInstanceRunning) { String query = s.toString().trim(); if (query.startsWith("http://")) { query = query.replace("http://", ""); } if (query.startsWith("https://")) { query = query.replace("https://", ""); } if (oldSearch == null || !oldSearch.equals(s.toString().trim())) { searchInstanceRunning = true; InstanceSocialVM instanceSocialVM = new ViewModelProvider(ReorderTimelinesActivity.this).get(InstanceSocialVM.class); instanceSocialVM.getInstances(query).observe(ReorderTimelinesActivity.this, instanceSocialList -> { popupSearchInstanceBinding.searchInstance.setAdapter(null); String[] instances = new String[instanceSocialList.instances.size()]; int j = 0; for (InstanceSocial.Instance instance : instanceSocialList.instances) { instances[j] = instance.name; j++; } ArrayAdapter arrayAdapter = new ArrayAdapter<>(ReorderTimelinesActivity.this, android.R.layout.simple_list_item_1, instances); popupSearchInstanceBinding.searchInstance.setAdapter(arrayAdapter); if (popupSearchInstanceBinding.searchInstance.hasFocus() && !isFinishing()) popupSearchInstanceBinding.searchInstance.showDropDown(); if (oldSearch != null && oldSearch.equals(popupSearchInstanceBinding.searchInstance.getText().toString())) { popupSearchInstanceBinding.searchInstance.dismissDropDown(); } oldSearch = s.toString().trim(); searchInstanceRunning = false; }); } } } }; } @Override protected void onPause() { super.onPause(); if (changes) { //Update menu Bundle b = new Bundle(); b.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true); Intent intentBD = new Intent(Helper.BROADCAST_DATA); intentBD.putExtras(b); intentBD.setPackage(BuildConfig.APPLICATION_ID); sendBroadcast(intentBD); } if (bottomChanges) { Bundle b = new Bundle(); b.putBoolean(Helper.RECEIVE_REDRAW_BOTTOM, true); Intent intentBD = new Intent(Helper.BROADCAST_DATA); intentBD.putExtras(b); intentBD.setPackage(BuildConfig.APPLICATION_ID); sendBroadcast(intentBD); } } @Override public void onStartDrag(RecyclerView.ViewHolder viewHolder) { touchHelper.startDrag(viewHolder); } @Override public void onStop() { super.onStop(); } }