-
-
Notifications
You must be signed in to change notification settings - Fork 153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Show number of unread messages #378
base: master
Are you sure you want to change the base?
Conversation
fd8be94
to
c32c34c
Compare
setActionView(R.layout.action_menu_counter) | ||
actionView?.findViewById<TextView>( | ||
R.id.counter | ||
)?.text = applicationState.counterLabel |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current implementation of the Android App didn't store the messages even in memory, so there wasn't a way to render that data.
It does via viewModel.messages.get(appid)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, It is storing messages there, but this list is not populated when the app starts, then, if the user doesn't navigate to every app section, the list of messages is not updated.
Apart of that, the data within MessageStateHolder
is not "announced" outside of the instance, and the ViewModel
doesn't act as a ViewModel but as a "data provider" that you need to ask for data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, It is storing messages there, but this list is not populated when the app starts, then, if the user doesn't navigate to every app section, the list of messages is not updated.
I get the last part, but the first part seems a little dishonest. The current implementation doesn't populate all messages from all apps because it doesn't need to. It sounds like "It's not possible with the current implementation, so there must be a rewrite", which I don't buy, changing the current implementation to load all messages from all apps should be simpler than wholly rewriting the message handling.
(not saying the current implementation should be adjusted like this, it only about the argumentation)
@@ -77,6 +77,8 @@ dependencies { | |||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The server doesn't provide an easy way to obtain the number of unread messages on every application
I dislike not having the correct count and only having at least x message there. This should be supported server side by returning the message count in the /application endpoint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with you, but the backend doesn't provide this info.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't want this feature to be implemented client side, there must be server side support for this. The refactoring can probably be included without the changed behavior for the message count.
@@ -77,6 +77,8 @@ dependencies { | |||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' | |||
implementation 'androidx.vectordrawable:vectordrawable:1.2.0' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for you contribution and your effort! Sadly as is, this PR is too big for me to accept. The refactoring of the messages look cool, but it IMO doesn't really fix something with the previous implementation, it's just a different implementation that uses the StateFlow stuff.
I'm not against refactoring, but it's basically a switch from battle tested code, that's running like this for multiple years to something that will have bugs that will fall back onto me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new Implementation provides multiple things:
- A single SourceOfTruth where the data is stored/obtained
- A reactive approach that updates the UI in real time whenever any data is updated
- A clean separation of responsibility (eg: In the previous one, the UI wasn't only used to render the UI but even parse the API data that was received from the WS Connection)
- A Real MVVM implementation that allows the UI to subscribe to new data from the VM and the VM to be agnostic to what the View does.
I know the PR is a bit bigger, and just because I knew it, I split it into multiple commits that can be merged 1 by 1, keeping the app working as the previous commit worked.
If you want, I can split the PR into smaller PRs If it helps you on the review process
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was just a quick glance, I'll have to test this more in depth with different android api versions, and my last linux/android studio update broke my emulators, so it'll take some time.
* @property scope The coroutine scope where the repository will run. | ||
* @property baseUrl The base url of the server used to create the full url of the images. | ||
* @property applicationApi The application api used to interact with the server. | ||
* @property messageApi The message api used to interact with the server. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you remove all comments here, I don't think the provide much value as most of the stuff can be already read from the method / var signature.
* @property applicationApi The application api used to interact with the server. | ||
* @property messageApi The message api used to interact with the server. | ||
*/ | ||
class Repository |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* @property messageApi The message api used to interact with the server. | ||
*/ | ||
class Repository | ||
private constructor( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The previous implementation had info logging for "deleting message with id xy", "Loading more messages for appid" and concrete error logging when the fetching failed e.g "failed requesting messages", "Failed to delete message". These should be readded.
And the println("JcLog
should be removed.
|
||
companion object { | ||
|
||
private const val LIMIT = 20 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Revert to the old limit of 100.
operator fun invoke(context: Context): Settings = Settings( | ||
sharedPreferences = context.getSharedPreferences("gotify", Context.MODE_PRIVATE) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Write this as second constructor:
constructor(c: Context) : this(c.getSharedPreferences("gotify", Context.MODE_PRIVATE))
/** | ||
* Whether the messages are currently being refreshed. | ||
*/ | ||
val refreshing = _refreshing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should probably remove mutability.
val refreshing = _refreshing | |
val refreshing: StateFlow<Boolean> = _refreshing |
@@ -171,52 +201,56 @@ internal class MessagesActivity : | |||
|
|||
private fun refreshAll() { | |||
CoilInstance.evict(this) | |||
startActivity(Intent(this, InitializationActivity::class.java)) | |||
finish() | |||
messagesViewModel.onRefreshAll() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Revert this back to the cal to the InitializationActivity, as this will also reset the WebSocketConnection.
*/ | ||
class Repository | ||
private constructor( | ||
private val scope: CoroutineScope, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The currently selected application isn't highlighted in the navigation drawer.
noselect.webm
false -> emptyList() | ||
true -> messageApi.getAppMessages(application.id, limit, currentPagingState.since) | ||
.awaitResponse().takeIf { it.isSuccessful }?.body() | ||
?.also { paging.value += (application.id to it.paging.toPagingState()) }?.also { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of the .also in this class make this hard to read, especially if it also nested in also. I'd prefer putting these into variables and then using them without nesting.
( | ||
appId to ( | ||
( | ||
previous.associateBy { | ||
it.id | ||
} + messages.associateBy { | ||
it.id | ||
} | ||
).values.sortedByDescending { | ||
it.id | ||
} | ||
) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is difficult to understand, due to the weird formatting and many braces.
The server doesn't provide an easy way to obtain the number of unread messages on every application, and the current implementation of the Android App didn't store the messages even in memory, so there wasn't a way to render that data.
To achieve it, the first I needed to do was create a mechanism to store data in memory. For that, I have created a repository that keeps data in memory and provides the list of Applications/Messages in a reactive approach, which helps us in the update process of the UI in real-time.
The Repository is updated directly from the WebSocket connection, so the messages don't need to be parsed on different parts and we keep a Single-Source-of-Truth.
After that, I created a new ViewModel for the MessageScreen in a more "Android Approach" and refactored the MessageActivity, keeping the previous features and adding the number of unread messages on the sidebar.
Old unused classes have been removed, as they are not needed anymore.
Fix #313
🎨 UI Changes
Add relevant screenshots