mirror of
https://github.com/LiveFastEatTrashRaccoon/RaccoonForLemmy.git
synced 2025-02-08 22:18:38 +01:00
fix: pagination in tabbed screens (#610)
This commit is contained in:
parent
daaabd9f3f
commit
f14cafba22
58
README.md
58
README.md
@ -68,7 +68,7 @@ for some technical notes.
|
|||||||
- view post feed and comments with different listing and sort types;
|
- view post feed and comments with different listing and sort types;
|
||||||
- possibility to upvote and downvote (with configurable swipe actions);
|
- possibility to upvote and downvote (with configurable swipe actions);
|
||||||
- community and user detail (with info about moderators/moderated communities);
|
- community and user detail (with info about moderators/moderated communities);
|
||||||
- user profile with one's own posts, comments and saved items;
|
- review your posts and comments (created by you, bookmarked, liked/disliked);
|
||||||
- inbox with replies, mentions and direct messages;
|
- inbox with replies, mentions and direct messages;
|
||||||
- global search with different result types (all, posts, comments, user, communities);
|
- global search with different result types (all, posts, comments, user, communities);
|
||||||
- create and edit new posts (with optional images);
|
- create and edit new posts (with optional images);
|
||||||
@ -85,56 +85,51 @@ for some technical notes.
|
|||||||
- multi-community (community aggregation);
|
- multi-community (community aggregation);
|
||||||
- view the moderation log;
|
- view the moderation log;
|
||||||
- community moderation tool (examine and resolve reports, ban users, feature posts, block
|
- community moderation tool (examine and resolve reports, ban users, feature posts, block
|
||||||
further comments from posts, mark comments as distinguished, remove posts/comments);
|
further comments from posts, mark comments as distinguished, remove posts/comments, examine all posts/comments created
|
||||||
- save posts and comments you are creating as draft to edit them later.
|
in your communities);
|
||||||
|
- save posts and comments you are creating as drafts to edit them later;
|
||||||
|
|
||||||
Most clients for Lemmy currently offer the first points (with various degrees of completion), so
|
Most clients for Lemmy currently offer the first points (with various degrees of completion), so
|
||||||
there is nothing special about Raccoon for Lemmy, whereas the last ones are less common and are
|
there is nothing special about Raccoon for Lemmy, whereas the last ones are less common and are
|
||||||
directed to more demanding users.
|
directed to more demanding users.
|
||||||
|
|
||||||
Concerning customization, the ability to change some aspects like font face or size and app
|
Concerning customization, the ability to change some aspects like font face or size and app colors, vote format, bar
|
||||||
colors, vote format, bar transparency and so on was of paramount importance from the very beginning.
|
transparency and so on was of paramount importance from the very beginning. Similarly, users should be able to use the
|
||||||
Similarly, users should be able to use the app in their native language and change the UI language
|
app in their native language and change the UI language independently of the system language.
|
||||||
independently from the system language.
|
|
||||||
|
|
||||||
This app is also intended for moderators who want to use their mobile device, offering moderation
|
This app is also intended for moderators who want to use their mobile device, offering moderation tools (feature post,
|
||||||
tools (feature post, lock post, distinguish comment, remove post/comment, ban users) and the ability
|
lock post, distinguish comment, remove post/comment, ban users) and the ability to revert any of these actions.
|
||||||
to revert any of these actions.
|
|
||||||
|
|
||||||
The project is under active development, so expect new features to be added over time. Have a
|
The project is under active development, so expect new features to be added over time. Have a look on the issues labeled
|
||||||
look on the issues labeled with "feature" in the issue tracker to get an idea of what's going to
|
with "feature" in the issue tracker to get an idea of what's going to come next.
|
||||||
come next.
|
|
||||||
|
|
||||||
If you have ideas, feedback, suggestions or comments remember to speak up and use your
|
If you have ideas, feedback, suggestions or comments remember to speak up and use your voice. You can add reports or
|
||||||
voice. You can add reports or request features and they will be considered.
|
request features and they will be considered.
|
||||||
|
|
||||||
## Why was the project started?
|
## Why was the project started?
|
||||||
|
|
||||||
Because raccoons are so adorable, aren't they? 🦝🦝🦝
|
Because raccoons are so adorable, aren't they? 🦝🦝🦝
|
||||||
|
|
||||||
Joking apart, one of the main goals was to experiment with KMP and learn how to properly deal
|
Joking apart, one of the main goals was to experiment with KMP and learn how to properly deal with the challenges of a
|
||||||
with the challenges of a multiplatform environment, and a medium-sized project like this was an
|
multiplatform environment, and a medium-sized project like this was an ideal testing ground for that technology.
|
||||||
ideal testing ground for that technology.
|
|
||||||
|
|
||||||
Secondly, I felt that the Android ecosystem of Lemmy apps was a little "poor" with few
|
Secondly, I felt that the Android ecosystem of Lemmy apps was a little "poor" with few native apps (fewer open source),
|
||||||
native apps (fewer open source), while the "market" is dominated by iOS and cross platform clients.
|
while the "market" is dominated by iOS and cross-platform clients. I️ ❤️ Kotlin, I ❤️ Free and Open Source Software and
|
||||||
I️ ❤️ Kotlin, I ❤️ Free and Open Source Software and I ❤️ native app development, so there was a
|
I ❤️ native app development, so there was a niche that could be filled.
|
||||||
niche that needed to be filled.
|
|
||||||
|
|
||||||
Developing a new client was an opportunity to add all the good features that were "scattered" across
|
Developing a new client was an opportunity to add all the good features that were "scattered" across different apps,
|
||||||
different apps, e.g. the feature richness of [Liftoff](https://github.com/liftoff-app/liftoff), the
|
e.g. the feature richness of [Liftoff](https://github.com/liftoff-app/liftoff), the
|
||||||
multi-community feature of
|
multi-community feature of
|
||||||
[Summit](https://github.com/idunnololz/summit-for-lemmy) and the polished UI of the really great
|
[Summit](https://github.com/idunnololz/summit-for-lemmy) and the polished UI of the really great
|
||||||
[Thunder](https://github.com/thunder-app/thunder) and so on. This app tries to be configurable
|
[Thunder](https://github.com/thunder-app/thunder) and so on. This app tries to be configurable
|
||||||
enough to make users feel "at home" and choose what they want, while at the same time having a not
|
enough to make users feel "at home" and choose what they want, while at the same time having a not
|
||||||
too cluttered interface (except for the Settings screen - I know!)
|
too cluttered interface (except for the Settings screen - I know!)
|
||||||
|
|
||||||
In the third place, this app has been a means to dig deeper inside Lemmy's internals and become more
|
In the third place, this app has been a means to dig deeper inside Lemmy's internals and become more humble and patient
|
||||||
humble and patient towards other apps because there are technical difficulties in having to deal
|
towards other apps because there are technical difficulties in having to deal with a platform like Lemmy.
|
||||||
with a platform like Lemmy.
|
|
||||||
|
|
||||||
This involves a high level of discretion and personal taste, I know, but this project _is_ all
|
This involves a high level of discretion and personal taste, I know, but this project _is_ all about experimenting and
|
||||||
about experimenting and learning.
|
learning.
|
||||||
|
|
||||||
## Technical notes:
|
## Technical notes:
|
||||||
|
|
||||||
@ -165,8 +160,7 @@ the [CONTRIBUTING.md](https://github.com/diegoberaldin/RaccoonForLemmy/blob/mast
|
|||||||
feel confident with repository forks, pull requests, managing resource files, etc. feel free to
|
feel confident with repository forks, pull requests, managing resource files, etc. feel free to
|
||||||
drop an email or contact me in any way.
|
drop an email or contact me in any way.
|
||||||
|
|
||||||
Please remember: every contribution is welcome and everyone's opinion matters here. This is a
|
Please remember: every contribution is welcome and everyone's opinion matters here. This is a community project, open
|
||||||
community project, open source, ad-free and free of charge, and it belongs to us all so don't be
|
source, ad-free and free of charge, and it belongs to us all so don't be afraid to get involved.
|
||||||
afraid to get involved.
|
|
||||||
|
|
||||||
And don't forget every 🦝's motto: «Live Fast, Eat Trash» (abbreviated L.F.E.T.).
|
And don't forget every 🦝's motto: «Live Fast, Eat Trash» (abbreviated L.F.E.T.).
|
||||||
|
@ -231,7 +231,7 @@
|
|||||||
<string name="report_list_type_unresolved">Unresolved</string>
|
<string name="report_list_type_unresolved">Unresolved</string>
|
||||||
<string name="report_action_resolve">Resolve</string>
|
<string name="report_action_resolve">Resolve</string>
|
||||||
<string name="report_action_unresolve">Unresolve</string>
|
<string name="report_action_unresolve">Unresolve</string>
|
||||||
<string name="sidebar_not_logged_message">Welcome to Raccoon for Lemmy!\n\nIn anonymous mode, use the drop down button (▼) above to change instance.\n\nYou can log in to your intance at any time from the Profile screen.\n\nEnjoy Lemmy!</string>
|
<string name="sidebar_not_logged_message">Welcome to Raccoon for Lemmy!\n\nIn anonymous mode, use the drop down button (▼) above to change instance.\n\nYou can log in to your instance at any time from the Profile screen.\n\nEnjoy Lemmy!</string>
|
||||||
<string name="settings_default_inbox_type">Default inbox type</string>
|
<string name="settings_default_inbox_type">Default inbox type</string>
|
||||||
<string name="mod_action_add_mod">Add moderator</string>
|
<string name="mod_action_add_mod">Add moderator</string>
|
||||||
<string name="mod_action_remove_mod">Remove moderator</string>
|
<string name="mod_action_remove_mod">Remove moderator</string>
|
||||||
|
@ -1,55 +1,48 @@
|
|||||||
## Module structure
|
## Module structure
|
||||||
|
|
||||||
The project has different kinds of modules and, depending on the group a module belongs to, there
|
The project has different kinds of modules and, depending on the group a module belongs to, there are some rules about
|
||||||
are some rules about which other modules it can depend on.
|
which other modules it can depend on.
|
||||||
|
|
||||||
Here is a description of the dependency flow:
|
Here is a description of the dependency flow:
|
||||||
|
|
||||||
- `:androidApp` which is the KMP equivalent of `:app` module in Android-only projects)
|
- `:androidApp` which is the KMP equivalent of `:app` module in Android-only projects) include `:shared` and can
|
||||||
include `:shared` and can include `:core` modules (e.g. for navigation);
|
include `:core` modules (e.g. for navigation);
|
||||||
- `:shared` is the heart of the KMP application and it virtually includes every other Gradle module
|
- `:shared` is the heart of the KMP application and it virtually includes every other Gradle module as a dependency (it
|
||||||
as a dependency (it contains in the `DiHelper.kt` files the setup of the DI so it basically needs
|
contains in the `DiHelper.kt` files the setup of the DI, so it basically needs to see all Koin modules);
|
||||||
to see all Koin modules);
|
- `:feature` modules are included by :shared and include :domain, :core and :unit modules, but they DO not include other
|
||||||
- `:feature` modules are included by :shared and include :domain, :core and :unit modules but they
|
each other nor any top level module; some unit modules are used just by one feature (e.g. `:unit:postlist` is used
|
||||||
DO not include other each other nor any top level module; some unit modules are used just by one
|
only by `:feature:home`) in some other cases multiple features use the same unit (e.g. `:unit:zoomableimage` is used
|
||||||
feature (e.g. `:unit:postlist` is used only by `:feature:home`) in some other cases multiple
|
by both `:feature:home`, `:feature:search`, `:feature:profile` and `:feature:inbox`);
|
||||||
features use the same unit (e.g. `:unit:zoomableimage` is used by
|
- `:domain` modules can be used by feature and unit modules and can only include core modules; only exception
|
||||||
both `:feature:home`, `:feature:search`, `:feature:profile` and `:feature:inbox`):
|
is `:domain:inbox` which is a thin layer on top of `:domain:lemmy` so it depends on it (for inbox related functions);
|
||||||
- `:domain` modules can be used by feature and unit modules and can only include core modules; only
|
- `:unit` modules are included by feature modules (and `:shared`) and sometimes by other unit modules in case of highly
|
||||||
exception is `:domain:inbox` which is a thin layer on top of `:domain:lemmy` so it depends on it (
|
reusable parts of the app; the only notable violation to this rule is `:core:commonui:detailopener-impl` which is a
|
||||||
for inbox related functions);
|
special module because it is only included by `:shared` (which does the binding between `:detailopener-api`
|
||||||
- `:unit` modules are included by feature modules (and `:shared`) and sometimes by other unit
|
and `:detailopener-impl`) and it includes some unit modules but the fact of a unit module included by a core module in
|
||||||
modules in case of highly reusable parts of the app; the only notable violation to this rule
|
general should never happen (instead, the reverse is perfectly ok);
|
||||||
is `:core:commonui:detailopener-impl` which is a special module because it is only included
|
|
||||||
by `:shared` (which does the binding between `:detailopener-api` and `:detailopener-impl`) and it
|
|
||||||
includes some unit modules but the fact of a unit module included by a core module in general
|
|
||||||
should never happen (instead, the reverse is perfectly ok);
|
|
||||||
- `:core` modules can sometimes include each other (but without cycles, e.g. `:core:markdown`
|
- `:core` modules can sometimes include each other (but without cycles, e.g. `:core:markdown`
|
||||||
includes `:core:commonui:components` / `:core:utils` because it is a mid-level module and
|
includes `:core:commonui:components` / `:core:utils` because it is a mid-level module and something similar happens
|
||||||
something similar happens with `:core:persistecnce` which
|
with `:core:persistecnce` which uses `:core:preferences` / `:core:appearance`) and nothing else; they are in turn
|
||||||
uses `:core:preferences` / `:core:appearance`) and nothing else; they are in turn used by all the
|
used by all the other types of modules.
|
||||||
other types of modules.
|
|
||||||
|
|
||||||
### Top-level modules
|
### Top-level modules
|
||||||
|
|
||||||
The main module (Android-specific) is `:androidApp`, which contains the Application
|
The main module (Android-specific) is `:androidApp`, which contains the Application subclass (`MainApplication`) and the
|
||||||
subclass (`MainApplication`) and the main activity (`MainActivity`). The latter in
|
main activity (`MainActivity`). The latter in its `onCreate(Bundle?)` invokes the `MainView` Composable function which
|
||||||
its `onCreate(Bundle?)` invokes the `MainView` Composable function which in turns calls `App`, the
|
in turns calls `App`, the main entry point of the multiplatform application which is defined in the `:shared` module.
|
||||||
main entry point of the multiplatform application which is defined in the `:shared` module.
|
|
||||||
|
|
||||||
`:shared` is the top module of the multiplatform application, which includes all the other modules
|
`:shared` is the top module of the multiplatform application, which includes all the other modules and is not included
|
||||||
and is not included by anything (except `:androidApp`). In its `commonMain` source set, this module
|
by anything (except `:androidApp`). In its `commonMain` source set, this module contains `App`, the application entry
|
||||||
contains `App`, the application entry point, the definition on the `MainScreen` (and its ViewModel)
|
point, the definition on the `MainScreen` (and its ViewModel) hosting the main navigation with the bottom tab bar.
|
||||||
hosting the main navigation with the bottom tab bar. Another important part of this module resides
|
Another important part of this module resides in the platform specific source sets (`androidMain` and `iosMain`
|
||||||
in the platform specific source sets (`androidMain` and `iosMain` respectively) where
|
respectively) where two `DiHelper.kt` files (one for each platform) can be found, which contain the setup of the root of
|
||||||
two `DiHelper.kt` files (one for each platform) can be found, which contain the setup of the root of
|
the project's dependency injection in a platform specific way, an initialization function on iOS and a Koin module for
|
||||||
the project's dependency injection in a platform specific way, an initialization function on iOS and
|
Android (which is included in `MainApplication`).
|
||||||
a Koin module for Android (which is included in `MainApplication`).
|
|
||||||
|
|
||||||
### Feature modules
|
### Feature modules
|
||||||
|
|
||||||
These modules correspond to the main functions of the application, i.e. the sections of the main
|
These modules correspond to the main functions of the application, i.e. the sections of the main bottom navigation. In
|
||||||
bottom navigation. In particular:
|
particular:
|
||||||
|
|
||||||
- `:feature:home` contains the post list tab;
|
- `:feature:home` contains the post list tab;
|
||||||
- `:feature:search` contains the Explore tab;
|
- `:feature:search` contains the Explore tab;
|
||||||
@ -61,25 +54,23 @@ bottom navigation. In particular:
|
|||||||
|
|
||||||
These are purely business logic modules that can be reused to provide application main parts:
|
These are purely business logic modules that can be reused to provide application main parts:
|
||||||
|
|
||||||
- `:domain:identity` contains the repositories and use cases that are related to user identity,
|
- `:domain:identity` contains the repositories and use cases that are related to user identity, authorization and API
|
||||||
authorization and API configuration;
|
configuration;
|
||||||
- `:domain:lemmy` contains all the Lemmy API interaction logic and is divided into two submodules:
|
- `:domain:lemmy` contains all the Lemmy API interaction logic and is divided into two submodules:
|
||||||
- `:data` contains all the domain models for Lemmy entities (posts, comments, communities,
|
- `:data` contains all the domain models for Lemmy entities (posts, comments, communities, users, etc);
|
||||||
users, etc);
|
- `:repository` contains the repositories that access Lemmy APIs (through the :core:api module) and are used
|
||||||
- `:repository` contains the repositories that access Lemmy APIs (through the :core:api module)
|
to manage the entities contained in the :data module;
|
||||||
and are used to manage the entities contained in the :data module;
|
- `:domain:inbox` contains some uses cases needed to interact with the replies, mentions and private messages
|
||||||
- `:domain:inbox` contains some uses cases needed to interact with the replies, mentions and private
|
repositories and coordinate the interaction between inbox-related app components.
|
||||||
messages repositories and coordinate the interaction between inbox-related app components.
|
|
||||||
|
|
||||||
### Unit modules
|
### Unit modules
|
||||||
|
|
||||||
These modules are the building blocks that are used to create user-visible parts of the application,
|
These modules are the building blocks that are used to create user-visible parts of the application, i.e. the various
|
||||||
i.e. the various screens, some of which are reusable in multiple points (e.g. the user detail,
|
screens, some of which are reusable in multiple points (e.g. the user detail, community detail or post detail, but also
|
||||||
community detail or post detail, but also report/post/comment creation forms, etc.). In some cases
|
report/post/comment creation forms, etc.). In some cases even a dialog or a bottom-sheet can become a "unit", especially
|
||||||
even a dialog or a bottom-sheet can become a "unit", especially if it is used in multiple points or
|
if it is used in multiple points or contains a little more than pure UI (e.g. some presentation logic); simple pure-UI
|
||||||
contains a little more than pure UI (e.g. some presentation logic); simple pure-UI dialogs and
|
dialogs and sheets are located in the `:core:commonui:modals` module instead (but are being progressively converted to
|
||||||
sheets are located in the `:core:commonui:modals` module instead (but are being progressively
|
separate units).
|
||||||
converted to separate units).
|
|
||||||
|
|
||||||
Here is a list of the main unit modules and their purpose:
|
Here is a list of the main unit modules and their purpose:
|
||||||
|
|
||||||
@ -97,6 +88,7 @@ Here is a list of the main unit modules and their purpose:
|
|||||||
- `:unit:createpost` contains the create post form
|
- `:unit:createpost` contains the create post form
|
||||||
- `:unit:drafts` contains the screen uses to display post and comment drafts
|
- `:unit:drafts` contains the screen uses to display post and comment drafts
|
||||||
- `:unit:drawer` contains the navigation drawer
|
- `:unit:drawer` contains the navigation drawer
|
||||||
|
- `:unit:filteredcontents` contains the screen to access moderated contents or liked/disliked contents
|
||||||
- `:unit:instanceinfo` contains the instance info bottom sheet with the list of communities
|
- `:unit:instanceinfo` contains the instance info bottom sheet with the list of communities
|
||||||
- `:unit:login` contains the login modal bottom sheet
|
- `:unit:login` contains the login modal bottom sheet
|
||||||
- `:unit:manageaccounts` contains the modal bottom sheet used to change account
|
- `:unit:manageaccounts` contains the modal bottom sheet used to change account
|
||||||
@ -123,49 +115,48 @@ Here is a list of the main unit modules and their purpose:
|
|||||||
|
|
||||||
### Core modules
|
### Core modules
|
||||||
|
|
||||||
These are the foundational blocks containing the design system and various reusable utilities that
|
These are the foundational blocks containing the design system and various reusable utilities that are called throughout
|
||||||
are called throughout the whole project. Here is a short description of them:
|
the whole project. Here is a short description of them:
|
||||||
|
|
||||||
- `:core:api` contains the Ktorfit services used to interact with Lemmy APIs and all the data
|
- `:core:api` contains the Ktorfit services used to interact with Lemmy APIs and all the data transfer objects (DTOs)
|
||||||
transfer objects (DTOs) used to send and receive data from the APIs;
|
used to send and receive data from the APIs;
|
||||||
- `:core:appearance` contains the look and feel repository which exposes the information about the
|
- `:core:appearance` contains the look and feel repository which exposes the information about the current theme as
|
||||||
current theme as observable states and allows to change them;
|
observable states and allows to change them;
|
||||||
- `:core:architecture` contains the building blocks for the Model-View-Intent architecture used in
|
- `:core:architecture` contains the building blocks for the Model-View-Intent architecture used in all the screens of
|
||||||
all the screens of the application;
|
the application;
|
||||||
- `:core:commonui` contains a series of sub-modules that are used to define UI components used in
|
- `:core:commonui` contains a series of submodules that are used to define UI components used in the app and reusable
|
||||||
the app and reusable UI blocks:
|
UI blocks:
|
||||||
- `:components`: a collection of components that represent graphical widgets
|
- `:components`: a collection of components that represent graphical widgets
|
||||||
- `:detailopener-api` : a utility module used to expose an API to centralize content opening (
|
- `:detailopener-api` : a utility module used to expose an API to centralize content opening (post detail,
|
||||||
post detail, community, detail, user detail, comment creation and post creation)
|
community, detail, user detail, comment creation and post creation)
|
||||||
- `:detailopener-impl`: implementation of the detail opener, this is an exception to the module
|
- `:detailopener-impl`: implementation of the detail opener, this is an exception to the module architecture because
|
||||||
architecture because it is a core module which includes unit modules so the important thing is
|
it is a core module which includes unit modules so the important thing is
|
||||||
that no one **ever** include this module except for `:shared`;
|
that no one **ever** include this module except for `:shared`;
|
||||||
- `:lemmyui`: graphical components used to represent Lemmy UI (posts, comments, inbox items,
|
- `:lemmyui`: graphical components used to represent Lemmy UI (posts, comments, inbox items, etc.) and reusable
|
||||||
etc.) and reusable sub-components such as different types of headers, footers, cards, etc.
|
subcomponents such as different types of headers, footers, cards, etc.
|
||||||
- `:modals`: definition of modal bottom sheets and dialogs that have no presentation logic. This
|
- `:modals`: definition of modal bottom sheets and dialogs that have no presentation logic. This module was
|
||||||
module was historically much bigger and over time components were migrated to separate units
|
historically much bigger and over time components were migrated to separate units
|
||||||
modules;
|
modules;
|
||||||
- `:core:markdown` contains Markdown rendering logic;
|
- `:core:markdown` contains Markdown rendering logic;
|
||||||
- `core:l10n` contains all the localization messages and the `L10nManager` interface which acts
|
- `core:l10n` contains all the localization messages and the `L10nManager` interface which acts as a wrapper around
|
||||||
as a wrapper around Lyricist to load the internationalized messages;
|
Lyricist to load the internationalized messages;
|
||||||
- `:core:navigation` contains the navigation manager used for stack navigation, bottom sheet
|
- `:core:navigation` contains the navigation manager used for stack navigation, bottom sheet navigation and a
|
||||||
navigation and a coordinator for the events originated by the navigation drawer;
|
coordinator for the events originated by the navigation drawer;
|
||||||
- `:core:notifications` contains the `NotificationCenter` contract and implementation as well as the
|
- `:core:notifications` contains the `NotificationCenter` contract and implementation as well as the event definition,
|
||||||
event definition, this is used as an event bus throughout the whole project;
|
this is used as an event bus throughout the whole project;
|
||||||
- `:core:persistence` contains the local database (primary storage) management logic as well as
|
- `:core:persistence` contains the local database (primary storage) management logic as well as SQLDelight definitions
|
||||||
SQLDelight definitions of entities and migrations, plus all the local data sources that are used
|
of entities and migrations, plus all the local data sources that are used
|
||||||
to access the database;
|
to access the database;
|
||||||
- `:core:preferences` contains the shared preferences/user defaults (secondary storage) and relies
|
- `:core:preferences` contains the shared preferences/user defaults (secondary storage) and relies on the
|
||||||
on the multiplatform-settings library to offer a temporary key-value store;
|
multiplatform-settings library to offer a temporary key-value store;
|
||||||
- `:core:resources` is a wrapper around the resource loading (fonts and images mainly) which used to
|
- `:core:resources` is a wrapper around the resource loading (fonts and images mainly) which used to rely on an external
|
||||||
rely on an external library and now used the built-in resource management of Compose;
|
library and now used the built-in resource management of Compose;
|
||||||
- `:core:utils`: contains a series of helper and utility functions/classes that are used in the
|
- `:core:utils`: contains a series of helper and utility functions/classes that are used in the project but were not big
|
||||||
project but were not big enough to be converted to separate domain/core modules on their own.
|
enough to be converted to separate domain/core modules on their own.
|
||||||
|
|
||||||
On second thoughts:
|
On second thoughts:
|
||||||
|
|
||||||
- `:core:commonui` has still too much in it, especially `:modals` packages should become unit
|
- `:core:commonui` has still too much in it, especially `:modals` packages should become unit modules;
|
||||||
modules;
|
- `:core:persistence` belongs more to domain modules, e.g. `:domain:accounts`/`:domain:settings` but it is implemented
|
||||||
- `:core:persistence` belongs more to domain modules, e.g. `:domain:accounts`/`:domain:settings` but
|
as a core module because is is strongly tied to SQLDelight and its generated code which provides the named queries to
|
||||||
it is implemented as a core module because is is strongly tied to SQLDelight and its generated
|
fetch/save data to the local DB.
|
||||||
code which provides the named queries to fetch/save data to the local DB.
|
|
||||||
|
@ -41,7 +41,7 @@ class FilteredContentsViewModel(
|
|||||||
initialState = FilteredContentsMviModel.State(),
|
initialState = FilteredContentsMviModel.State(),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private var currentPage = 1
|
private val currentPage = mutableMapOf<FilteredContentsSection, Int>()
|
||||||
private var pageCursor: String? = null
|
private var pageCursor: String? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -157,7 +157,8 @@ class FilteredContentsViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun refresh(initial: Boolean = false) {
|
private fun refresh(initial: Boolean = false) {
|
||||||
currentPage = 1
|
currentPage[FilteredContentsSection.Posts] = 1
|
||||||
|
currentPage[FilteredContentsSection.Comments] = 1
|
||||||
pageCursor = null
|
pageCursor = null
|
||||||
updateState {
|
updateState {
|
||||||
it.copy(
|
it.copy(
|
||||||
@ -198,11 +199,12 @@ class FilteredContentsViewModel(
|
|||||||
val refreshing = currentState.refreshing
|
val refreshing = currentState.refreshing
|
||||||
|
|
||||||
if (currentState.section == FilteredContentsSection.Posts) {
|
if (currentState.section == FilteredContentsSection.Posts) {
|
||||||
|
val page = currentPage[FilteredContentsSection.Posts] ?: 1
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
val itemList = async {
|
val itemList = async {
|
||||||
postRepository.getAll(
|
postRepository.getAll(
|
||||||
auth = auth,
|
auth = auth,
|
||||||
page = currentPage,
|
page = page,
|
||||||
pageCursor = pageCursor,
|
pageCursor = pageCursor,
|
||||||
type = ListingType.ModeratorView,
|
type = ListingType.ModeratorView,
|
||||||
sort = SortType.New,
|
sort = SortType.New,
|
||||||
@ -227,21 +229,18 @@ class FilteredContentsViewModel(
|
|||||||
}
|
}
|
||||||
}.await()
|
}.await()
|
||||||
val comments = async {
|
val comments = async {
|
||||||
if (currentPage == 1 && (currentState.comments.isEmpty() || refreshing)) {
|
if (page == 1 && (currentState.comments.isEmpty() || refreshing)) {
|
||||||
// this is needed because otherwise on first selector change
|
// this is needed because otherwise on first selector change
|
||||||
// the lazy column scrolls back to top (it must have an empty data set)
|
// the lazy column scrolls back to top (it must have an empty data set)
|
||||||
commentRepository.getAll(
|
commentRepository.getAll(
|
||||||
auth = auth,
|
auth = auth,
|
||||||
page = currentPage,
|
page = 1,
|
||||||
type = ListingType.ModeratorView,
|
type = ListingType.ModeratorView,
|
||||||
).orEmpty()
|
).orEmpty()
|
||||||
} else {
|
} else {
|
||||||
currentState.comments
|
currentState.comments
|
||||||
}
|
}
|
||||||
}.await()
|
}.await()
|
||||||
if (!itemList.isNullOrEmpty()) {
|
|
||||||
currentPage++
|
|
||||||
}
|
|
||||||
val itemsToAdd = itemList.orEmpty().filter { post ->
|
val itemsToAdd = itemList.orEmpty().filter { post ->
|
||||||
!post.deleted
|
!post.deleted
|
||||||
}
|
}
|
||||||
@ -268,13 +267,14 @@ class FilteredContentsViewModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (!itemList.isNullOrEmpty()) {
|
if (!itemList.isNullOrEmpty()) {
|
||||||
currentPage++
|
currentPage[FilteredContentsSection.Posts] = page + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
val page = currentPage[FilteredContentsSection.Comments] ?: 1
|
||||||
val itemList = commentRepository.getAll(
|
val itemList = commentRepository.getAll(
|
||||||
auth = auth,
|
auth = auth,
|
||||||
page = currentPage,
|
page = page,
|
||||||
type = ListingType.ModeratorView,
|
type = ListingType.ModeratorView,
|
||||||
)?.let { list ->
|
)?.let { list ->
|
||||||
if (refreshing) {
|
if (refreshing) {
|
||||||
@ -305,7 +305,7 @@ class FilteredContentsViewModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (!itemList.isNullOrEmpty()) {
|
if (!itemList.isNullOrEmpty()) {
|
||||||
currentPage++
|
currentPage[FilteredContentsSection.Comments] = page + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -317,11 +317,12 @@ class FilteredContentsViewModel(
|
|||||||
val refreshing = currentState.refreshing
|
val refreshing = currentState.refreshing
|
||||||
|
|
||||||
if (currentState.section == FilteredContentsSection.Posts) {
|
if (currentState.section == FilteredContentsSection.Posts) {
|
||||||
|
val page = currentPage[FilteredContentsSection.Posts] ?: 1
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
val itemList = async {
|
val itemList = async {
|
||||||
userRepository.getLikedPosts(
|
userRepository.getLikedPosts(
|
||||||
auth = auth,
|
auth = auth,
|
||||||
page = currentPage,
|
page = page,
|
||||||
pageCursor = pageCursor,
|
pageCursor = pageCursor,
|
||||||
liked = currentState.liked,
|
liked = currentState.liked,
|
||||||
sort = SortType.New,
|
sort = SortType.New,
|
||||||
@ -346,12 +347,12 @@ class FilteredContentsViewModel(
|
|||||||
}
|
}
|
||||||
}.await()
|
}.await()
|
||||||
val comments = async {
|
val comments = async {
|
||||||
if (currentPage == 1 && (currentState.comments.isEmpty() || refreshing)) {
|
if (page == 1 && (currentState.comments.isEmpty() || refreshing)) {
|
||||||
// this is needed because otherwise on first selector change
|
// this is needed because otherwise on first selector change
|
||||||
// the lazy column scrolls back to top (it must have an empty data set)
|
// the lazy column scrolls back to top (it must have an empty data set)
|
||||||
userRepository.getLikedComments(
|
userRepository.getLikedComments(
|
||||||
auth = auth,
|
auth = auth,
|
||||||
page = currentPage,
|
page = 1,
|
||||||
liked = currentState.liked,
|
liked = currentState.liked,
|
||||||
sort = SortType.New,
|
sort = SortType.New,
|
||||||
).orEmpty()
|
).orEmpty()
|
||||||
@ -359,9 +360,6 @@ class FilteredContentsViewModel(
|
|||||||
currentState.comments
|
currentState.comments
|
||||||
}
|
}
|
||||||
}.await()
|
}.await()
|
||||||
if (!itemList.isNullOrEmpty()) {
|
|
||||||
currentPage++
|
|
||||||
}
|
|
||||||
val itemsToAdd = itemList.orEmpty().filter { post ->
|
val itemsToAdd = itemList.orEmpty().filter { post ->
|
||||||
!post.deleted
|
!post.deleted
|
||||||
}
|
}
|
||||||
@ -388,13 +386,14 @@ class FilteredContentsViewModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (!itemList.isNullOrEmpty()) {
|
if (!itemList.isNullOrEmpty()) {
|
||||||
currentPage++
|
currentPage[FilteredContentsSection.Posts] = page + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
val page = currentPage[FilteredContentsSection.Comments] ?: 1
|
||||||
val itemList = userRepository.getLikedComments(
|
val itemList = userRepository.getLikedComments(
|
||||||
auth = auth,
|
auth = auth,
|
||||||
page = currentPage,
|
page = page,
|
||||||
liked = currentState.liked,
|
liked = currentState.liked,
|
||||||
sort = SortType.New,
|
sort = SortType.New,
|
||||||
)?.let { list ->
|
)?.let { list ->
|
||||||
@ -426,7 +425,7 @@ class FilteredContentsViewModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (!itemList.isNullOrEmpty()) {
|
if (!itemList.isNullOrEmpty()) {
|
||||||
currentPage++
|
currentPage[FilteredContentsSection.Comments] = page + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ class ProfileLoggedViewModel(
|
|||||||
initialState = ProfileLoggedMviModel.UiState()
|
initialState = ProfileLoggedMviModel.UiState()
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private var currentPage = 1
|
private val currentPage = mutableMapOf<ProfileLoggedSection, Int>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
updateState { it.copy(instance = apiConfigurationRepository.instance.value) }
|
updateState { it.copy(instance = apiConfigurationRepository.instance.value) }
|
||||||
@ -209,7 +209,8 @@ class ProfileLoggedViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun refresh(initial: Boolean = false) {
|
private suspend fun refresh(initial: Boolean = false) {
|
||||||
currentPage = 1
|
currentPage[ProfileLoggedSection.Posts] = 1
|
||||||
|
currentPage[ProfileLoggedSection.Comments] = 1
|
||||||
updateState {
|
updateState {
|
||||||
it.copy(
|
it.copy(
|
||||||
canFetchMore = true,
|
canFetchMore = true,
|
||||||
@ -245,23 +246,24 @@ class ProfileLoggedViewModel(
|
|||||||
val section = currentState.section
|
val section = currentState.section
|
||||||
val includeNsfw = settingsRepository.currentSettings.value.includeNsfw
|
val includeNsfw = settingsRepository.currentSettings.value.includeNsfw
|
||||||
if (section == ProfileLoggedSection.Posts) {
|
if (section == ProfileLoggedSection.Posts) {
|
||||||
|
val page = currentPage[ProfileLoggedSection.Posts] ?: 1
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
val itemList = async {
|
val itemList = async {
|
||||||
userRepository.getPosts(
|
userRepository.getPosts(
|
||||||
auth = auth,
|
auth = auth,
|
||||||
id = userId,
|
id = userId,
|
||||||
page = currentPage,
|
page = page,
|
||||||
sort = SortType.New,
|
sort = SortType.New,
|
||||||
)
|
)
|
||||||
}.await()
|
}.await()
|
||||||
val comments = async {
|
val comments = async {
|
||||||
if (currentPage == 1 && (currentState.comments.isEmpty() || refreshing)) {
|
if (page == 1 && (currentState.comments.isEmpty() || refreshing)) {
|
||||||
// this is needed because otherwise on first selector change
|
// this is needed because otherwise on first selector change
|
||||||
// the lazy column scrolls back to top (it must have an empty data set)
|
// the lazy column scrolls back to top (it must have an empty data set)
|
||||||
userRepository.getComments(
|
userRepository.getComments(
|
||||||
auth = auth,
|
auth = auth,
|
||||||
id = userId,
|
id = userId,
|
||||||
page = currentPage,
|
page = 1,
|
||||||
sort = SortType.New,
|
sort = SortType.New,
|
||||||
).orEmpty()
|
).orEmpty()
|
||||||
} else {
|
} else {
|
||||||
@ -299,14 +301,15 @@ class ProfileLoggedViewModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (!itemList.isNullOrEmpty()) {
|
if (!itemList.isNullOrEmpty()) {
|
||||||
currentPage++
|
currentPage[ProfileLoggedSection.Posts] = page + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
val page = currentPage[ProfileLoggedSection.Comments] ?: 1
|
||||||
val itemList = userRepository.getComments(
|
val itemList = userRepository.getComments(
|
||||||
auth = auth,
|
auth = auth,
|
||||||
id = userId,
|
id = userId,
|
||||||
page = currentPage,
|
page = page,
|
||||||
sort = SortType.New,
|
sort = SortType.New,
|
||||||
)
|
)
|
||||||
val commentsToAdd = itemList.orEmpty()
|
val commentsToAdd = itemList.orEmpty()
|
||||||
@ -328,7 +331,7 @@ class ProfileLoggedViewModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (!itemList.isNullOrEmpty()) {
|
if (!itemList.isNullOrEmpty()) {
|
||||||
currentPage++
|
currentPage[ProfileLoggedSection.Comments] = page + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ class ReportListViewModel(
|
|||||||
initialState = ReportListMviModel.UiState(),
|
initialState = ReportListMviModel.UiState(),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private var currentPage = 1
|
private val currentPage = mutableMapOf<ReportListSection, Int>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launch {
|
screenModelScope.launch {
|
||||||
@ -98,7 +98,8 @@ class ReportListViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun refresh(initial: Boolean = false) {
|
private fun refresh(initial: Boolean = false) {
|
||||||
currentPage = 1
|
currentPage[ReportListSection.Posts] = 1
|
||||||
|
currentPage[ReportListSection.Comments] = 1
|
||||||
updateState {
|
updateState {
|
||||||
it.copy(
|
it.copy(
|
||||||
canFetchMore = true,
|
canFetchMore = true,
|
||||||
@ -124,23 +125,24 @@ class ReportListViewModel(
|
|||||||
val section = currentState.section
|
val section = currentState.section
|
||||||
val unresolvedOnly = currentState.unresolvedOnly
|
val unresolvedOnly = currentState.unresolvedOnly
|
||||||
if (section == ReportListSection.Posts) {
|
if (section == ReportListSection.Posts) {
|
||||||
|
val page = currentPage[ReportListSection.Posts] ?: 1
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
val itemList = async {
|
val itemList = async {
|
||||||
postRepository.getReports(
|
postRepository.getReports(
|
||||||
auth = auth,
|
auth = auth,
|
||||||
communityId = communityId,
|
communityId = communityId,
|
||||||
page = currentPage,
|
page = page,
|
||||||
unresolvedOnly = unresolvedOnly,
|
unresolvedOnly = unresolvedOnly,
|
||||||
)
|
)
|
||||||
}.await()
|
}.await()
|
||||||
val commentReports = async {
|
val commentReports = async {
|
||||||
if (currentPage == 1 && (currentState.commentReports.isEmpty() || refreshing)) {
|
if (page == 1 && (currentState.commentReports.isEmpty() || refreshing)) {
|
||||||
// this is needed because otherwise on first selector change
|
// this is needed because otherwise on first selector change
|
||||||
// the lazy column scrolls back to top (it must have an empty data set)
|
// the lazy column scrolls back to top (it must have an empty data set)
|
||||||
commentRepository.getReports(
|
commentRepository.getReports(
|
||||||
auth = auth,
|
auth = auth,
|
||||||
communityId = communityId,
|
communityId = communityId,
|
||||||
page = currentPage,
|
page = 1,
|
||||||
unresolvedOnly = unresolvedOnly,
|
unresolvedOnly = unresolvedOnly,
|
||||||
).orEmpty()
|
).orEmpty()
|
||||||
} else {
|
} else {
|
||||||
@ -163,14 +165,15 @@ class ReportListViewModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (!itemList.isNullOrEmpty()) {
|
if (!itemList.isNullOrEmpty()) {
|
||||||
currentPage++
|
currentPage[ReportListSection.Posts] = page + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
val page = currentPage[ReportListSection.Comments] ?: 1
|
||||||
val itemList = commentRepository.getReports(
|
val itemList = commentRepository.getReports(
|
||||||
auth = auth,
|
auth = auth,
|
||||||
communityId = communityId,
|
communityId = communityId,
|
||||||
page = currentPage,
|
page = page,
|
||||||
unresolvedOnly = unresolvedOnly,
|
unresolvedOnly = unresolvedOnly,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -189,7 +192,7 @@ class ReportListViewModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (!itemList.isNullOrEmpty()) {
|
if (!itemList.isNullOrEmpty()) {
|
||||||
currentPage++
|
currentPage[ReportListSection.Comments] = page + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ class UserDetailViewModel(
|
|||||||
initialState = UserDetailMviModel.UiState(),
|
initialState = UserDetailMviModel.UiState(),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private var currentPage = 1
|
private val currentPage = mutableMapOf<UserDetailSection, Int>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
updateState {
|
updateState {
|
||||||
@ -247,7 +247,8 @@ class UserDetailViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun refresh(initial: Boolean = false) {
|
private suspend fun refresh(initial: Boolean = false) {
|
||||||
currentPage = 1
|
currentPage[UserDetailSection.Posts] = 1
|
||||||
|
currentPage[UserDetailSection.Comments] = 1
|
||||||
updateState {
|
updateState {
|
||||||
it.copy(
|
it.copy(
|
||||||
canFetchMore = true,
|
canFetchMore = true,
|
||||||
@ -281,25 +282,26 @@ class UserDetailViewModel(
|
|||||||
val userId = currentState.user.id
|
val userId = currentState.user.id
|
||||||
val includeNsfw = settingsRepository.currentSettings.value.includeNsfw
|
val includeNsfw = settingsRepository.currentSettings.value.includeNsfw
|
||||||
if (section == UserDetailSection.Posts) {
|
if (section == UserDetailSection.Posts) {
|
||||||
|
val page = currentPage[UserDetailSection.Posts] ?: 1
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
val itemList = async {
|
val itemList = async {
|
||||||
userRepository.getPosts(
|
userRepository.getPosts(
|
||||||
auth = auth,
|
auth = auth,
|
||||||
id = userId,
|
id = userId,
|
||||||
page = currentPage,
|
page = page,
|
||||||
sort = currentState.sortType,
|
sort = currentState.sortType,
|
||||||
username = uiState.value.user.name,
|
username = uiState.value.user.name,
|
||||||
otherInstance = otherInstance,
|
otherInstance = otherInstance,
|
||||||
)
|
)
|
||||||
}.await()
|
}.await()
|
||||||
val comments = async {
|
val comments = async {
|
||||||
if (currentPage == 1 && (currentState.comments.isEmpty() || refreshing)) {
|
if (page == 1 && (currentState.comments.isEmpty() || refreshing)) {
|
||||||
// this is needed because otherwise on first selector change
|
// this is needed because otherwise on first selector change
|
||||||
// the lazy column scrolls back to top (it must have an empty data set)
|
// the lazy column scrolls back to top (it must have an empty data set)
|
||||||
userRepository.getComments(
|
userRepository.getComments(
|
||||||
auth = auth,
|
auth = auth,
|
||||||
id = userId,
|
id = userId,
|
||||||
page = currentPage,
|
page = 1,
|
||||||
sort = currentState.sortType,
|
sort = currentState.sortType,
|
||||||
username = uiState.value.user.name,
|
username = uiState.value.user.name,
|
||||||
otherInstance = otherInstance,
|
otherInstance = otherInstance,
|
||||||
@ -346,14 +348,15 @@ class UserDetailViewModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (!itemList.isNullOrEmpty()) {
|
if (!itemList.isNullOrEmpty()) {
|
||||||
currentPage++
|
currentPage[UserDetailSection.Posts] = page + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
val page = currentPage[UserDetailSection.Comments] ?: 1
|
||||||
val itemList = userRepository.getComments(
|
val itemList = userRepository.getComments(
|
||||||
auth = auth,
|
auth = auth,
|
||||||
id = userId,
|
id = userId,
|
||||||
page = currentPage,
|
page = page,
|
||||||
sort = currentState.sortType,
|
sort = currentState.sortType,
|
||||||
otherInstance = otherInstance,
|
otherInstance = otherInstance,
|
||||||
)
|
)
|
||||||
@ -376,7 +379,7 @@ class UserDetailViewModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (!itemList.isNullOrEmpty()) {
|
if (!itemList.isNullOrEmpty()) {
|
||||||
currentPage++
|
currentPage[UserDetailSection.Comments] = page + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user