package com.anabasis.vkchatmanager; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.text.Editable; import android.text.TextWatcher; import android.view.MenuItem; import android.view.View; import android.widget.Toast; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowCompat; import androidx.core.view.WindowInsetsCompat; import androidx.viewpager2.widget.ViewPager2; import com.anabasis.vkchatmanager.adapters.ChatTabAdapter; import com.anabasis.vkchatmanager.dialogs.MultiLinkDialog; import com.anabasis.vkchatmanager.model.VkChat; import com.anabasis.vkchatmanager.network.TokenExpiredException; import com.anabasis.vkchatmanager.network.VkApiClient; import com.anabasis.vkchatmanager.util.TokenManager; import com.google.android.material.button.MaterialButton; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.progressindicator.LinearProgressIndicator; import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayoutMediator; import com.google.android.material.textfield.TextInputEditText; import org.json.JSONArray; import org.json.JSONObject; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private VkApiClient api; private final List office = new ArrayList<>(); private final List retail = new ArrayList<>(); private final List warehouse = new ArrayList<>(); private final List coffee = new ArrayList<>(); private final List other = new ArrayList<>(); private final List userIdsToProcess = new ArrayList<>(); private final List userNamesToProcess = new ArrayList<>(); private LinearProgressIndicator progressBar; private androidx.swiperefreshlayout.widget.SwipeRefreshLayout swipeRefresh; private ViewPager2 pager; private ChatTabAdapter adapter; private MaterialButton listBtn, addBtn, removeBtn, showUsersBtn; private TextInputEditText singleInput; private final Handler handler = new Handler(Looper.getMainLooper()); private Runnable delayedProcessing; private final ActivityResultLauncher authLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == RESULT_OK) { recreate(); } } ); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); WindowCompat.setDecorFitsSystemWindows(getWindow(), false); setContentView(R.layout.activity_main); setSupportActionBar(findViewById(R.id.toolbar)); ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.appBar), (v, windowInsets) -> { Insets systemBarInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBarInsets.left, systemBarInsets.top, systemBarInsets.right, 0); return windowInsets; }); ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.content_container), (v, windowInsets) -> { Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.ime() | WindowInsetsCompat.Type.systemBars()); v.setPadding(0, 0, 0, insets.bottom); return windowInsets; }); String token = TokenManager.load(this); if (token == null) { authLauncher.launch(new Intent(this, AuthActivity.class)); return; } api = new VkApiClient(token); pager = findViewById(R.id.pager); swipeRefresh = findViewById(R.id.swipeRefresh); swipeRefresh.setOnRefreshListener(this::refreshChats); TabLayout tabs = findViewById(R.id.tabs); listBtn = findViewById(R.id.multiLinkBtn); addBtn = findViewById(R.id.addBtn); removeBtn = findViewById(R.id.removeBtn); showUsersBtn = findViewById(R.id.showUsersBtn); singleInput = findViewById(R.id.singleLinkInput); progressBar = findViewById(R.id.progressBar); setupTabs(tabs); refreshChats(); listBtn.setOnClickListener(v -> MultiLinkDialog .newInstance(this::processLinksList) .show(getSupportFragmentManager(), "links") ); singleInput.addTextChangedListener(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) { if (delayedProcessing != null) { handler.removeCallbacks(delayedProcessing); } } @Override public void afterTextChanged(Editable s) { String link = s.toString().trim(); if (!link.isEmpty()) { delayedProcessing = () -> { processLinksList(List.of(link)); s.clear(); }; handler.postDelayed(delayedProcessing, 1000); } } }); addBtn.setOnClickListener(v -> processUsers(true)); removeBtn.setOnClickListener(v -> processUsers(false)); showUsersBtn.setOnClickListener(v -> showUsers()); } @Override public boolean onCreateOptionsMenu(android.view.Menu menu) { getMenuInflater().inflate(R.menu.main_menu, menu); return true; } @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { ChatListFragment f = getCurrentFragment(); int id = item.getItemId(); if (id == R.id.action_refresh) { refreshChats(); return true; } if (id == R.id.action_select_all) { if (f != null) f.setAll(true); return true; } if (id == R.id.action_deselect_all) { if (f != null) f.setAll(false); return true; } if (id == R.id.action_token_status) { showTokenStatus(); return true; } if (id == R.id.action_sign_out) { TokenManager.clear(this); recreate(); return true; } return super.onOptionsItemSelected(item); } /* ===================== TOKEN ===================== */ private void handleTokenExpired() { Toast.makeText(this, getString(R.string.token_expired_message), Toast.LENGTH_SHORT).show(); TokenManager.clear(this); recreate(); } /* ===================== UI ===================== */ private void showTokenStatus() { long exp = TokenManager.expiration(this); String message; if (exp == 0) { message = getString(R.string.token_status_perpetual); } else { long now = System.currentTimeMillis(); if (exp > now) { long diff = (exp - now) / 1000; long hours = diff / 3600; long minutes = (diff % 3600) / 60; message = getString(R.string.token_status_expires_in, hours, minutes); } else { message = getString(R.string.token_status_expired); } } new MaterialAlertDialogBuilder(this) .setTitle(R.string.token_status_title) .setMessage(message) .setPositiveButton(R.string.ok_button, null) .show(); } private void setUiEnabled(boolean enabled) { listBtn.setEnabled(enabled); addBtn.setEnabled(enabled); removeBtn.setEnabled(enabled); showUsersBtn.setEnabled(enabled); singleInput.setEnabled(enabled); swipeRefresh.setEnabled(enabled); } private void setupTabs(TabLayout tabs) { List> pages = List.of( office, retail, warehouse, coffee, other ); adapter = new ChatTabAdapter(this, pages); pager.setAdapter(adapter); pager.setOffscreenPageLimit(5); new TabLayoutMediator(tabs, pager, (tab, pos) -> { switch (pos) { case 0: tab.setText(getString(R.string.office_tab)); break; case 1: tab.setText(getString(R.string.retail_tab)); break; case 2: tab.setText(getString(R.string.warehouse_tab)); break; case 3: tab.setText(getString(R.string.coffee_tab)); break; case 4: tab.setText(getString(R.string.other_tab)); break; } }).attach(); } private ChatListFragment getCurrentFragment() { int pos = pager.getCurrentItem(); long itemId = adapter.getItemId(pos); return (ChatListFragment) getSupportFragmentManager() .findFragmentByTag("f" + itemId); } /* ===================== REFRESH ===================== */ private void refreshChats() { setUiEnabled(false); progressBar.setVisibility(View.VISIBLE); progressBar.setIndeterminate(true); new Thread(() -> { try { JSONArray items = api.getChats(); office.clear(); retail.clear(); warehouse.clear(); coffee.clear(); other.clear(); for (int i = 0; i < items.length(); i++) { JSONObject conv = items.getJSONObject(i) .getJSONObject("conversation"); if (!"chat".equals( conv.getJSONObject("peer").getString("type"))) continue; int chatId = conv.getJSONObject("peer").getInt("local_id"); String title = conv .getJSONObject("chat_settings") .getString("title"); addChat(chatId, title); } runOnUiThread(() -> { setUiEnabled(true); progressBar.setIndeterminate(false); progressBar.setVisibility(View.GONE); swipeRefresh.setRefreshing(false); adapter.notifyDataChanged(); Toast.makeText( this, getString(R.string.chats_updated_message), Toast.LENGTH_SHORT ).show(); }); } catch (TokenExpiredException e) { runOnUiThread(this::handleTokenExpired); } catch (Exception e) { runOnUiThread(() -> { setUiEnabled(true); progressBar.setIndeterminate(false); progressBar.setVisibility(View.GONE); swipeRefresh.setRefreshing(false); Toast.makeText( this, getString(R.string.api_error, e.getMessage()), Toast.LENGTH_LONG ).show(); }); } }).start(); } private void addChat(int id, String title) { String t = title.toLowerCase(); if (t.startsWith("ag ")) t = t.substring(3); VkChat c = new VkChat(id, title); if (t.contains("офис")) office.add(c); else if (t.contains("розница")) retail.add(c); else if (t.contains("склад")) warehouse.add(c); else if (t.contains("кофейни")) coffee.add(c); else other.add(c); } /* ===================== LINKS ===================== */ private void processLinksList(List links) { userIdsToProcess.clear(); userNamesToProcess.clear(); setUiEnabled(false); new Thread(() -> { for (String link : links) { try { String screen = extractScreenName(link); int uid = api.resolveUserId(screen); if (uid > 0) { userIdsToProcess.add(uid); userNamesToProcess.add(api.getUserName(uid)); } } catch (TokenExpiredException e) { runOnUiThread(this::handleTokenExpired); } catch (Exception ignored) {} } runOnUiThread(() -> { setUiEnabled(true); Toast.makeText( this, getString(R.string.users_loaded_message, userIdsToProcess.size()), Toast.LENGTH_SHORT ).show(); }); }).start(); } private String extractScreenName(String link) { link = link.trim(); if (link.startsWith("@")) return link.substring(1); link = link.replace("https://", "") .replace("http://", ""); if (link.startsWith("vk.com/")) return link.substring(7); if (link.startsWith("m.vk.com/")) return link.substring(8); return link; } /* ===================== ADD / REMOVE ===================== */ private void showUsers() { if (userNamesToProcess.isEmpty()) { Toast.makeText(this, getString(R.string.user_list_empty), Toast.LENGTH_SHORT).show(); return; } StringBuilder msg = new StringBuilder(); for (String name : userNamesToProcess) { msg.append("• ").append(name).append("\n"); } new MaterialAlertDialogBuilder(this) .setTitle(getString(R.string.users_dialog_title)) .setMessage(msg.toString()) .setPositiveButton(getString(R.string.ok_button), null) .show(); } private void processUsers(boolean add) { List chats = getCurrentFragment().getSelected(); if (chats.isEmpty() || userIdsToProcess.isEmpty()) { Toast.makeText(this, getString(R.string.no_chats_or_users_selected_message), Toast.LENGTH_SHORT).show(); return; } StringBuilder msg = new StringBuilder(); msg.append(add ? getString(R.string.add_users_dialog_message) : getString(R.string.remove_users_dialog_message)); msg.append("\n\n"); for (String name : userNamesToProcess) { msg.append("• ").append(name).append("\n"); } msg.append("\n"); msg.append(getString(R.string.in_chats_dialog_message)); msg.append("\n"); for (VkChat c : chats) { msg.append("• ").append(c.title).append("\n"); } new MaterialAlertDialogBuilder(this) .setTitle(getString(R.string.confirmation_dialog_title)) .setMessage(msg.toString()) .setPositiveButton(getString(R.string.confirm_button), (d, w) -> executeUsers(add, chats)) .setNegativeButton(getString(R.string.cancel_button), null) .show(); } private void executeUsers(boolean add, List chats) { setUiEnabled(false); progressBar.setVisibility(View.VISIBLE); progressBar.setIndeterminate(false); progressBar.setProgress(0, true); int totalOps = chats.size() * userIdsToProcess.size(); progressBar.setMax(totalOps); new Thread(() -> { final List details = new ArrayList<>(); int done = 0; for (VkChat c : chats) { for (int i = 0; i < userIdsToProcess.size(); i++) { int uid = userIdsToProcess.get(i); String userName = userNamesToProcess.get(i); String resultMessage; try { if (add) { api.addUser(c.id, uid, true); resultMessage = getString(R.string.op_success_add_format, userName, c.title); } else { api.removeUser(c.id, uid); resultMessage = getString(R.string.op_success_remove_format, userName, c.title); } } catch (TokenExpiredException e) { runOnUiThread(this::handleTokenExpired); return; } catch (Exception e) { String error = e.getMessage() != null && !e.getMessage().isEmpty() ? e.getMessage() : getString(R.string.op_unknown_error); resultMessage = getString(R.string.op_failure_format, userName, c.title, error); } details.add(resultMessage); done++; int progress = done; runOnUiThread(() -> progressBar.setProgress(progress, true)); } } runOnUiThread(() -> { setUiEnabled(true); progressBar.setVisibility(View.GONE); ChatListFragment f = getCurrentFragment(); if (f != null) { f.showSnackbar(getString(R.string.operation_complete_message), details); } else { Toast.makeText( this, getString(R.string.operation_complete_message), Toast.LENGTH_LONG ).show(); } }); }).start(); } }