package io.heckel.ntfy.ui import android.Manifest import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.app.AlertDialog import android.content.ActivityNotFoundException import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.os.Bundle import android.provider.Settings import android.text.method.LinkMovementMethod import android.view.ActionMode import android.view.Menu import android.view.MenuItem import android.view.View import android.widget.Button import android.widget.TextView import android.widget.Toast import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.work.* import com.google.android.material.floatingactionbutton.FloatingActionButton import io.heckel.ntfy.BuildConfig import io.heckel.ntfy.R import io.heckel.ntfy.app.Application import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.Subscription import io.heckel.ntfy.firebase.FirebaseMessenger import io.heckel.ntfy.msg.ApiService import io.heckel.ntfy.msg.DownloadManager import io.heckel.ntfy.msg.DownloadType import io.heckel.ntfy.msg.NotificationDispatcher import io.heckel.ntfy.service.SubscriberService import io.heckel.ntfy.service.SubscriberServiceManager import io.heckel.ntfy.util.* import io.heckel.ntfy.work.DeleteWorker import io.heckel.ntfy.work.PollWorker import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.util.* import java.util.concurrent.TimeUnit import kotlin.random.Random class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.SubscribeListener, NotificationFragment.NotificationSettingsListener { private val viewModel by viewModels { SubscriptionsViewModelFactory((application as Application).repository) } private val repository by lazy { (application as Application).repository } private val api = ApiService() private val messenger = FirebaseMessenger() // UI elements private lateinit var menu: Menu private lateinit var mainList: RecyclerView private lateinit var mainListContainer: SwipeRefreshLayout private lateinit var adapter: MainAdapter private lateinit var fab: FloatingActionButton // Other stuff private var actionMode: ActionMode? = null private var workManager: WorkManager? = null // Context-dependent private var dispatcher: NotificationDispatcher? = null // Context-dependent private var appBaseUrl: String? = null // Context-dependent override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) Log.init(this) // Init logs in all entry points Log.d(TAG, "Create $this") // Dependencies that depend on Context workManager = WorkManager.getInstance(this) dispatcher = NotificationDispatcher(this, repository) appBaseUrl = getString(R.string.app_base_url) // Action bar title = getString(R.string.main_action_bar_title) // Floating action button ("+") fab = findViewById(R.id.fab) fab.setOnClickListener { onSubscribeButtonClick() } // Swipe to refresh mainListContainer = findViewById(R.id.main_subscriptions_list_container) mainListContainer.setOnRefreshListener { refreshAllSubscriptions() } mainListContainer.setColorSchemeResources(Colors.refreshProgressIndicator) // Update main list based on viewModel (& its datasource/livedata) val noEntries: View = findViewById(R.id.main_no_subscriptions) val onSubscriptionClick = { s: Subscription -> onSubscriptionItemClick(s) } val onSubscriptionLongClick = { s: Subscription -> onSubscriptionItemLongClick(s) } mainList = findViewById(R.id.main_subscriptions_list) adapter = MainAdapter(repository, onSubscriptionClick, onSubscriptionLongClick) mainList.adapter = adapter viewModel.list().observe(this) { it?.let { subscriptions -> // Update main list adapter.submitList(subscriptions as MutableList) if (it.isEmpty()) { mainListContainer.visibility = View.GONE noEntries.visibility = View.VISIBLE } else { mainListContainer.visibility = View.VISIBLE noEntries.visibility = View.GONE } // Add scrub terms to log (in case it gets exported) subscriptions.forEach { s -> Log.addScrubTerm(shortUrl(s.baseUrl), Log.TermType.Domain) Log.addScrubTerm(s.topic) } // Update banner + WebSocket banner showHideBatteryBanner(subscriptions) showHideWebSocketBanner(subscriptions) } } // Add scrub terms to log (in case it gets exported) // FIXME this should be in Log.getFormatted repository.getUsersLiveData().observe(this) { it?.let { users -> users.forEach { u -> Log.addScrubTerm(shortUrl(u.baseUrl), Log.TermType.Domain) Log.addScrubTerm(u.username, Log.TermType.Username) Log.addScrubTerm(u.password, Log.TermType.Password) } } } // Scrub terms for last topics // FIXME this should be in Log.getFormatted repository.getLastShareTopics().forEach { topicUrl -> maybeSplitTopicUrl(topicUrl)?.let { Log.addScrubTerm(shortUrl(it.first), Log.TermType.Domain) Log.addScrubTerm(shortUrl(it.second), Log.TermType.Term) } } // React to changes in instant delivery setting viewModel.listIdsWithInstantStatus().observe(this) { SubscriberServiceManager.refresh(this) } // Battery banner val batteryBanner = findViewById(R.id.main_banner_battery) // Banner visibility is toggled in onResume() val dontAskAgainButton = findViewById