diff --git a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt index 85e4c47d..891d4fd5 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt @@ -110,6 +110,7 @@ import com.futo.platformplayer.stores.StringStorage import com.futo.platformplayer.stores.SubscriptionStorage import com.futo.platformplayer.stores.v2.ManagedStore import com.futo.platformplayer.views.ToastView +import com.futo.platformplayer.views.notification.NotificationOverlayView import com.futo.polycentric.core.ApiMethods import com.google.gson.JsonParser import com.google.zxing.integration.android.IntentIntegrator @@ -201,6 +202,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { lateinit var _fragLibraryVideos: LibraryVideosFragment; lateinit var _fragLibrarySearch: LibrarySearchFragment; lateinit var _fragLibraryFiles: LibraryFilesFragment; + lateinit var _fragNotifications: NotificationOverlayView.Frag; lateinit var _fragSettings: SettingsFragment; lateinit var _fragDeveloper: DeveloperFragment; lateinit var _fragLogin: LoginFragment; @@ -389,6 +391,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { _fragLibraryVideos = LibraryVideosFragment.newInstance(); _fragLibraryFiles = LibraryFilesFragment.newInstance(); _fragLibrarySearch = LibrarySearchFragment.newInstance(); + _fragNotifications = NotificationOverlayView.Frag(); _fragSettings = SettingsFragment.newInstance(); _fragDeveloper = DeveloperFragment.newInstance(); _fragLogin = LoginFragment.newInstance(); @@ -538,6 +541,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { _fragLibrarySearch.topBar = _fragTopBarSearch; _fragSettings.topBar = _fragTopBarNavigation; _fragDeveloper.topBar = _fragTopBarNavigation; + _fragNotifications.topBar = _fragTopBarGeneral; _fragBrowser.topBar = _fragTopBarNavigation; @@ -1368,6 +1372,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { LibraryVideosFragment::class -> _fragLibraryVideos as T; LibraryFilesFragment::class -> _fragLibraryFiles as T; LibrarySearchFragment::class -> _fragLibrarySearch as T; + NotificationOverlayView.Frag::class -> _fragNotifications as T; SettingsFragment:: class -> _fragSettings as T; DeveloperFragment::class -> _fragDeveloper as T; LoginFragment::class -> _fragLogin as T; diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/FeedView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/FeedView.kt index d49ae1e0..d0df0d9e 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/FeedView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/FeedView.kt @@ -47,7 +47,7 @@ abstract class FeedView : L private val _progressBar: ProgressBar; private val _spinnerSortBy: Spinner; private val _containerSortBy: LinearLayout; - private val _announcementView: AnnouncementView; + //private val _announcementView: AnnouncementView; private val _tagsView: TagsView; private val _textCentered: TextView; private val _emptyPagerContainer: FrameLayout; @@ -87,7 +87,7 @@ abstract class FeedView : L _textCentered = findViewById(R.id.text_centered); _emptyPagerContainer = findViewById(R.id.empty_pager_container); _progressBar = findViewById(R.id.progress_bar); - _announcementView = findViewById(R.id.announcement_view) + //_announcementView = findViewById(R.id.announcement_view) _progressBar.inactiveColor = Color.TRANSPARENT; _swipeRefresh = findViewById(R.id.swipe_refresh); @@ -192,7 +192,7 @@ abstract class FeedView : L } protected fun showAnnouncementView() { - _announcementView.visibility = View.VISIBLE + //_announcementView.visibility = View.VISIBLE } private fun ensureEnoughContentVisible(filteredResults: List) { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/GeneralTopBarFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/GeneralTopBarFragment.kt index a496c668..0938a94d 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/GeneralTopBarFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/GeneralTopBarFragment.kt @@ -7,6 +7,9 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageButton import android.widget.ImageView +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.lifecycle.lifecycleScope import com.futo.platformplayer.R import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.fragment.mainactivity.main.CreatorsFragment @@ -17,18 +20,54 @@ import com.futo.platformplayer.fragment.mainactivity.main.PlaylistsFragment import com.futo.platformplayer.fragment.mainactivity.main.SuggestionsFragment import com.futo.platformplayer.fragment.mainactivity.main.SuggestionsFragmentData import com.futo.platformplayer.models.SearchType +import com.futo.platformplayer.states.StateAnnouncement import com.futo.platformplayer.views.casting.CastButton +import com.futo.platformplayer.views.notification.NotificationOverlayView +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch class GeneralTopBarFragment : TopFragment() { private var _buttonSearch: ImageButton? = null; private var _buttonCast: CastButton? = null; + private var _buttonNotifs: ConstraintLayout? = null; + private var _buttonNotifIcon: ImageView? = null; + private var _buttonNotifCount: TextView? = null; + + init { + StateAnnouncement.instance.onAnnouncementChanged.subscribe { + lifecycleScope?.launch(Dispatchers.Main) { + updateNotifCount(); + } + } + } + + fun updateNotifCount() { + val currentAnnouncements = StateAnnouncement.instance.getVisibleAnnouncements(); + if(currentAnnouncements.any()) + _buttonNotifCount?.let { + it.text = currentAnnouncements.size.toString(); + it.visibility = View.VISIBLE; + } + else + _buttonNotifCount?.let { + it.text = currentAnnouncements.size.toString(); + it.visibility = View.GONE; + } + } + override fun onShown(parameter: Any?) { if(currentMain is CreatorsFragment) { _buttonSearch?.setImageResource(R.drawable.ic_person_search_300w); } else { _buttonSearch?.setImageResource(R.drawable.ic_search_300w); } + if(currentMain is NotificationOverlayView.Frag) { + _buttonNotifIcon?.setImageResource(R.drawable.ic_notifications_filled) + } + else { + _buttonNotifIcon?.setImageResource(R.drawable.ic_notifications) + } } override fun onHide() { @@ -44,6 +83,16 @@ class GeneralTopBarFragment : TopFragment() { val buttonSearch: ImageButton = view.findViewById(R.id.button_search); _buttonCast = view.findViewById(R.id.button_cast); + _buttonNotifs = view.findViewById(R.id.button_notifs); + _buttonNotifIcon = view.findViewById(R.id.button_notifs_icon); + _buttonNotifCount = view.findViewById(R.id.button_notifs_count); + + updateNotifCount(); + + _buttonNotifs?.setOnClickListener { + navigate(); + } + buttonSearch.setOnClickListener { if(currentMain is CreatorsFragment) { navigate(SuggestionsFragmentData("", SearchType.CREATOR)); diff --git a/app/src/main/java/com/futo/platformplayer/states/StateAnnouncement.kt b/app/src/main/java/com/futo/platformplayer/states/StateAnnouncement.kt index 0c12d35a..c0ae1adf 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateAnnouncement.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateAnnouncement.kt @@ -1,13 +1,20 @@ package com.futo.platformplayer.states +import android.view.View +import android.view.WindowManager +import com.futo.platformplayer.R import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.dialogs.PluginUpdateDialog import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.models.ImageVariable import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer import com.futo.platformplayer.stores.FragmentedStorage import com.futo.platformplayer.stores.StringHashSetStorage import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json @@ -110,6 +117,45 @@ class StateAnnouncement { onAnnouncementChanged.emit(); } + //Special Announcements + fun registerPluginUpdate(oldConfig: SourcePluginConfig, newConfig: SourcePluginConfig) { + registerAnnouncementSession(SessionAnnouncement( + "update-plugin-" + UUID.randomUUID().toString(), + "${newConfig.name} update v${newConfig.version} available!", + "An update is available to upgrade from ${oldConfig.version} to ${newConfig.version}.", + AnnouncementType.SESSION, + null, "updates", "Update", StateAnnouncement.ACTION_UPDATE_PLUGIN, + null, null,oldConfig.id, + newConfig?.absoluteIconUrl?.let { ImageVariable.fromUrl(it) } + ).withExtraAction("Changelog", StateAnnouncement.ACTION_CHANGELOG, oldConfig.id)); + } + fun registerPluginUpdated(newConfig: SourcePluginConfig) { + registerAnnouncementSession(SessionAnnouncement( + "updated-plugin-" + UUID.randomUUID().toString(), + "${newConfig.name} updated to v${newConfig.version}!", + "You have succesfully been updater to v${newConfig.version}.", + AnnouncementType.SESSION, + null, "updates", null, null, + null, null,null, + newConfig?.absoluteIconUrl?.let { ImageVariable.fromUrl(it) } + ).withExtraAction("Changelog", StateAnnouncement.ACTION_CHANGELOG, newConfig.id)); + } + + fun registerLoading(title: String, description: String, icon: ImageVariable? = null): String { + val id = "loading-" + UUID.randomUUID().toString(); + registerAnnouncementSession(SessionAnnouncement( + id, + title, + description, + AnnouncementType.ONGOING, + null, "loading", null, null, + null, null,null, icon + )); + return id; + } + + + fun getVisibleAnnouncements(category: String? = null): List { synchronized(_lock) { if (category != null) { @@ -122,7 +168,9 @@ class StateAnnouncement { } } - fun closeAnnouncement(id: String) { + fun closeAnnouncement(id: String?) { + if(id == null) + return; val item: Announcement?; synchronized(_lock) { item = _announcementsStore.findItem { it.id == id }; @@ -164,6 +212,7 @@ class StateAnnouncement { cancelAction?.invoke(item); } } + onAnnouncementChanged?.emit(); } fun deleteAllAnnouncements() { @@ -194,7 +243,9 @@ class StateAnnouncement { onAnnouncementChanged.emit(); } - fun neverAnnouncement(id: String) { + fun neverAnnouncement(id: String?) { + if(id == null) + return; synchronized(_lock) { val item = _announcementsStore.findItem { it.id == id }; if (item != null && !_announcementsNever.contains(id)) @@ -208,19 +259,26 @@ class StateAnnouncement { _announcementsNever.save(); onAnnouncementChanged.emit(); } - fun actionAnnouncement(id: String) { + fun actionAnnouncement(id: String?, extra: Boolean = false) { + if(id == null) + return; val item = _announcementsStore.findItem { it.id == id } ?: _sessionAnnouncements[id]; if(item != null) - actionAnnouncement(item); + actionAnnouncement(item, extra); } - fun actionAnnouncement(item: Announcement) { + fun actionAnnouncement(item: Announcement, extra: Boolean = false) { + val actionId = if(!extra) item.actionId else if(item is SessionAnnouncement) item.extraActionId else null; + val actionData = if(!extra) item.actionData else if(item is SessionAnnouncement) item.extraActionData else null; + val action = _sessionActions[item.id]; if (action != null) { action(item); } else { - when (item.actionId) { + when (actionId) { ACTION_NEVER -> neverAnnouncement(item.id); ACTION_SOMETHING -> actionSomething(); + ACTION_CHANGELOG -> actionChangelog(actionData); + ACTION_UPDATE_PLUGIN -> actionUpdatePlugin(item.id, actionData); } } } @@ -251,6 +309,83 @@ class StateAnnouncement { } + private fun actionChangelog(id: String?) { + if(id == null) + return; + + StateApp.instance.contextOrNull?.let { context -> + StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { + val plugin = StatePlugins.instance.getPlugin(id); + if (plugin == null) + return@launch + val update = StatePlugins.instance.checkForUpdates(plugin.config); + if(update == null) + return@launch; + + StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { + UIDialogs.showChangelogDialog(context, update.version, update.changelog!!.filterKeys { it.toIntOrNull() != null } + .mapKeys { it.key.toInt() } + .mapValues { update.getChangelogString(it.key.toString()) ?: "" }); + } + } + } + } + private fun actionUpdatePlugin(notifId: String?, id: String?) { + if(id == null) + return; + val plugin = StatePlugins.instance.getPlugin(id); + if (plugin == null) + return + + closeAnnouncement(notifId); + val loadingId = registerLoading("Updating ${plugin.config.name}..", "An update is in progress for ${plugin.config.name}.", + if(plugin.config.absoluteIconUrl != null) ImageVariable.fromUrl(plugin.config.absoluteIconUrl!!) else null); + + + StateApp.instance.contextOrNull?.let { context -> + + StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { + try { + val update = StatePlugins.instance.checkForUpdates(plugin.config); + if (update == null) + return@launch; + + val client = ManagedHttpClient(); + client.setTimeout(10000); + val script = StatePlugins.instance.getScript(plugin.config.id) ?: ""; + val newScript = client.get(update.absoluteScriptUrl)?.body?.string(); + if(newScript.isNullOrEmpty()) + throw IllegalStateException("No script found"); + + if(true || plugin.config.isLowRiskUpdate(script, update, newScript)) { + StatePlugins.instance.installPluginBackground(context, StateApp.instance.scope, update, newScript, + { text: String, progress: Double -> }, + { ex -> + if(ex == null) { + registerPluginUpdated(update); + } + else { + UIDialogs.appToast("Update for ${update.name} failed\n" + ex.message); + } + StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { + closeAnnouncement(loadingId); + } + }); + } + else { + StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { + closeAnnouncement(loadingId); + UIDialogs.showPluginUpdateDialog(context, plugin.config, update); + } + } + } + catch(ex: Throwable) { + Logger.e(TAG, "Failed to trigger update from announcement", ex); + } + } + } + } + fun registerDefaultHandlerAnnouncement() { registerAnnouncement( "default-url-handler", @@ -279,6 +414,8 @@ class StateAnnouncement { const val ACTION_SOMETHING = "SOMETHING"; + const val ACTION_CHANGELOG = "CHANGELOG"; + const val ACTION_UPDATE_PLUGIN = "UPDATE_PLUGIN"; const val ACTION_NEVER = "NEVER"; private const val TAG = "StateAnnouncement"; } @@ -294,7 +431,8 @@ open class Announcement( val time: OffsetDateTime? = null, val category: String? = null, val actionName: String? = null, - val actionId: String? = null + val actionId: String? = null, + val actionData: String? = null ); class SessionAnnouncement( id: String, @@ -306,7 +444,9 @@ class SessionAnnouncement( actionName: String? = null, actionId: String? = null, val cancelName: String? = null, - val cancelActionId: String? = null + val cancelActionId: String? = null, + actionData: String? = null, + val icon: ImageVariable? = null ): Announcement( id= id, title = title, @@ -315,13 +455,26 @@ class SessionAnnouncement( time = time, category = category, actionName = actionName, - actionId = actionId -); + actionId = actionId, + actionData = actionData +) { + var extraActionName: String? = null; + var extraActionId: String? = null; + var extraActionData: String? = null; + + fun withExtraAction(name: String, id: String, data: String? = null): SessionAnnouncement { + extraActionName = name; + extraActionId = id; + extraActionData = data; + return this; + } +} enum class AnnouncementType(val value : Int) { DELETABLE(0), //Close button deletes announcement (generally for actions) RECURRING(1), //Shows up till never is pressed (generally for patchnotes etc) PERMANENT(2), //Shows up until deleted through other means (action) SESSION(3), //Not persistent, only during this session - SESSION_RECURRING(4); //Not persistent, only during this session, recurring id + SESSION_RECURRING(4), //Not persistent, only during this session, recurring id + ONGOING(5); } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt index ad58ffbb..b5fd97f0 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt @@ -43,6 +43,7 @@ import com.futo.platformplayer.logging.AndroidLogConsumer import com.futo.platformplayer.logging.FileLogConsumer import com.futo.platformplayer.logging.LogLevel import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.models.ImageVariable import com.futo.platformplayer.receivers.AudioNoisyReceiver import com.futo.platformplayer.services.DownloadService import com.futo.platformplayer.stores.FragmentedStorage @@ -732,8 +733,10 @@ class StateApp { )); for(update in updateAvailable) - if(StatePlatform.instance.isClientEnabled(update.first.id)) - UIDialogs.showPluginUpdateDialog(context, update.first, update.second); + if(StatePlatform.instance.isClientEnabled(update.first.id)) { + //UIDialogs.showPluginUpdateDialog(context, update.first, update.second); + StateAnnouncement.instance.registerPluginUpdate(update.first, update.second); + } } } } diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt index 38473023..b7c2be7a 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt @@ -116,7 +116,7 @@ class StatePlugins { _updatesAvailableMap = updatesAvailableFor return@withContext configs; } - private suspend fun checkForUpdates(c: SourcePluginConfig): SourcePluginConfig? = withContext(Dispatchers.IO) { + suspend fun checkForUpdates(c: SourcePluginConfig): SourcePluginConfig? = withContext(Dispatchers.IO) { val sourceUrl = c.sourceUrl ?: return@withContext null; Logger.i(TAG, "Check for source updates '${c.name}'."); diff --git a/app/src/main/java/com/futo/platformplayer/views/announcements/AnnouncementView.kt b/app/src/main/java/com/futo/platformplayer/views/announcements/AnnouncementView.kt index 68d38c4d..2d7491b8 100644 --- a/app/src/main/java/com/futo/platformplayer/views/announcements/AnnouncementView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/announcements/AnnouncementView.kt @@ -6,12 +6,10 @@ import android.view.View import android.widget.FrameLayout import android.widget.LinearLayout import android.widget.TextView -import androidx.constraintlayout.widget.ConstraintLayout import androidx.lifecycle.findViewTreeLifecycleOwner import androidx.lifecycle.lifecycleScope import com.futo.platformplayer.R import com.futo.platformplayer.constructs.Event0 -import com.futo.platformplayer.dp import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.states.Announcement import com.futo.platformplayer.states.AnnouncementType @@ -162,6 +160,10 @@ class AnnouncementView : LinearLayout { _textClose.visibility = View.VISIBLE; _textNever.visibility = View.VISIBLE; } + AnnouncementType.ONGOING -> { + _textClose.visibility = View.GONE; + _textNever.visibility = View.GONE; + } } if (announcement.time != null) { diff --git a/app/src/main/java/com/futo/platformplayer/views/notification/NotificationOverlayView.kt b/app/src/main/java/com/futo/platformplayer/views/notification/NotificationOverlayView.kt new file mode 100644 index 00000000..f18440da --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/notification/NotificationOverlayView.kt @@ -0,0 +1,212 @@ +package com.futo.platformplayer.views.notification + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.R +import com.futo.platformplayer.fragment.mainactivity.main.MainFragment +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.Announcement +import com.futo.platformplayer.states.AnnouncementType +import com.futo.platformplayer.states.SessionAnnouncement +import com.futo.platformplayer.states.StateAnnouncement +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.views.AnyAdapterView +import com.futo.platformplayer.views.AnyAdapterView.Companion.asAny +import com.futo.platformplayer.views.LoaderView +import com.futo.platformplayer.views.adapters.AnyAdapter +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class NotificationOverlayView: ConstraintLayout { + + lateinit var recycler: RecyclerView; + var adapterNotifications: AnyAdapterView; + + constructor(context: Context) : super(context) { + inflate(context, R.layout.overlay_notifications, this) + + recycler = findViewById(R.id.container_notifications); + adapterNotifications = recycler.asAny(RecyclerView.VERTICAL, false, { + + }); + + } + + fun onShown(parameter: Any?) { + val announcements = StateAnnouncement.instance.getVisibleAnnouncements(); + adapterNotifications.adapter.setData(announcements); + + StateAnnouncement.instance.onAnnouncementChanged.subscribe(this) { + StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { + Logger.i("NotificationOverlayView", "Announcements Changed"); + val adapter = adapterNotifications; + val announcements = StateAnnouncement.instance.getVisibleAnnouncements(); + adapter.adapter.setData(announcements); + } + } + } + + fun onResume() { + + } + + fun onPause() { + StateAnnouncement.instance.onAnnouncementChanged.remove(this); + } + + class ViewHolder(private val _viewGroup: ViewGroup) : AnyAdapter.AnyViewHolder( + LayoutInflater.from(_viewGroup.context).inflate( + R.layout.list_announcement, + _viewGroup, false)) { + + protected var _announcement: Announcement? = null; + protected val _textName: TextView + protected val _textMetadata: TextView; + protected val _icon: ImageView; + protected val _buttonIgnore: ImageView + protected val _buttonNever: LinearLayout + protected val _buttonAction: LinearLayout + protected val _buttonActionText: TextView + protected val _buttonExtra: LinearLayout + protected val _buttonExtraText: TextView + protected val _loader: LoaderView; + + init { + _textName = _view.findViewById(R.id.text_name); + _textMetadata = _view.findViewById(R.id.text_metadata); + _buttonIgnore = _view.findViewById(R.id.button_ignore); + _buttonNever = _view.findViewById(R.id.button_never); + _buttonAction = _view.findViewById(R.id.button_action); + _buttonActionText = _view.findViewById(R.id.button_action_text); + _buttonExtra = _view.findViewById(R.id.button_extra); + _buttonExtraText = _view.findViewById(R.id.button_extra_text); + _icon = _view.findViewById(R.id.icon); + _loader = _view.findViewById(R.id.loader); + + _buttonIgnore.setOnClickListener { + _announcement.let { + StateAnnouncement.instance.closeAnnouncement(it?.id); + } + } + _buttonNever.setOnClickListener { + _announcement.let { + StateAnnouncement.instance.neverAnnouncement(it?.id); + } + } + _buttonExtra.setOnClickListener { + _announcement.let { + StateAnnouncement.instance.actionAnnouncement(it?.id, true) + } + } + _buttonAction.setOnClickListener { + _announcement.let { + StateAnnouncement.instance.actionAnnouncement(it?.id); + } + } + } + + + + override fun bind(value: Announcement) { + _announcement = value; + + _textName.text = value.title; + _textMetadata.text = value.msg; + + if(value is SessionAnnouncement) { + if(value.icon != null) { + value.icon.setImageView(_icon); + _icon.visibility = View.VISIBLE; + } + else + _icon.visibility = View.GONE; + if(value.extraActionName != null && value.extraActionId != null) { + _buttonExtraText.text = value.extraActionName; + _buttonExtra.visibility = View.VISIBLE; + } + else + _buttonExtra.visibility = View.GONE; + + if(value.announceType == AnnouncementType.ONGOING) { + _buttonIgnore.visibility = View.GONE; + } + else { + _buttonIgnore.visibility = View.VISIBLE; + } + } + else { + _buttonExtra.visibility = View.GONE; + _icon.visibility = View.GONE; + _buttonIgnore.visibility = View.VISIBLE; + } + + if(value.announceType == AnnouncementType.ONGOING) { + _loader.visibility = View.VISIBLE; + _loader.start(); + } + else { + _loader.visibility = View.GONE; + _loader.stop(); + } + + _buttonNever.visibility = + if (value.announceType == AnnouncementType.RECURRING || value.announceType == AnnouncementType.SESSION_RECURRING) + View.VISIBLE + else + View.GONE; + + _buttonAction.visibility = + if(value.actionId != null && value.actionName != null) + View.VISIBLE; + else View.GONE; + + if(value.actionId != null && value.actionName != null) { + _buttonActionText.text = value.actionName; + } + } + + } + + + class Frag : MainFragment() { + override val isMainView : Boolean = true; + override val isTab: Boolean = true; + override val hasBottomBar: Boolean get() = true; + + private var _view: NotificationOverlayView? = null; + + override fun onShownWithView(parameter: Any?, isBack: Boolean) { + super.onShownWithView(parameter, isBack); + _view?.onShown(parameter); + } + + override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val view = NotificationOverlayView(requireContext()); + _view = view; + return view; + } + + override fun onDestroyMainView() { + super.onDestroyMainView(); + _view = null; + } + + override fun onResume() { + super.onResume() + _view?.onResume(); + } + + override fun onPause() { + super.onPause() + _view?.onPause(); + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/background_primary_round_20dp.xml b/app/src/main/res/drawable/background_primary_round_20dp.xml new file mode 100644 index 00000000..7ef91cd2 --- /dev/null +++ b/app/src/main/res/drawable/background_primary_round_20dp.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_notifications_filled.xml b/app/src/main/res/drawable/ic_notifications_filled.xml new file mode 100644 index 00000000..cd47b10a --- /dev/null +++ b/app/src/main/res/drawable/ic_notifications_filled.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_feed.xml b/app/src/main/res/layout/fragment_feed.xml index 8ea93ae9..69aaeec9 100644 --- a/app/src/main/res/layout/fragment_feed.xml +++ b/app/src/main/res/layout/fragment_feed.xml @@ -38,11 +38,12 @@ android:layout_height="wrap_content" android:orientation="vertical"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/overlay_notifications.xml b/app/src/main/res/layout/overlay_notifications.xml new file mode 100644 index 00000000..bb4a0db3 --- /dev/null +++ b/app/src/main/res/layout/overlay_notifications.xml @@ -0,0 +1,28 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b4e6f199..b2487a27 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -896,6 +896,7 @@ Creator thumbnail Clear search Search + Notifications Search icon Back button App icon