diff --git a/docs/docs/auto-docs/introduction.md b/docs/docs/auto-docs/introduction.md new file mode 100644 index 000000000..5d8b09511 --- /dev/null +++ b/docs/docs/auto-docs/introduction.md @@ -0,0 +1,6 @@ +--- +id: introduction +title: Placeholder +--- + +This is a placeholder page until we automate in-code documentation updates to this site. diff --git a/docs/docs/docs/flutter-testing-guide.md b/docs/docs/docs/flutter-testing-guide.md new file mode 100644 index 000000000..4edf83615 --- /dev/null +++ b/docs/docs/docs/flutter-testing-guide.md @@ -0,0 +1,274 @@ +--- +id: flutter-testing +title: Flutter Testing Guide +--- + + +## Introduction + +Tests are an essential part of software development. They help developers to verify the functionality of the code they write and ensure that it behaves as expected. Testing is a process of executing a program with the intent of finding errors. They help to catch bugs early in the development process, which saves time and effort in the long run. Writing tests also makes the code more reliable and maintainable. + +The cost of removing defects increases exponentially. A defect caught in requirement and design phase costs less to fix than an error caught in the software maintenance cycle. + + +## Understanding Corner Cases + +In software testing, a corner case refers to a scenario or input that is rare, extreme, or unusual, and is not typically encountered in normal usage. These cases often involve unexpected combinations of inputs or circumstances that can cause a system to behave in unexpected or undefined ways. For example, if a software system is designed to handle only positive integers, a corner case might involve testing what happens when a negative integer is entered as input. + +To ensure that your tests cover all corner cases, you should consider the different input values and edge cases that your code might encounter. For example, if you are testing a function that performs a calculation, you should test it with different input values, including negative numbers, zero, and large numbers. + +It's also a good idea to use boundary testing, where you test the boundaries between different input values. For example, if your function takes an input between 0 and 100, you should test it with values of 0, 1, 99, 100, and values just above and below these boundaries. + + +## Getting Started + +Following are the steps you have to follow in order to start writing and testing your code:- + +1. Add the `flutter_test` package to your `pubspec.yaml` file. + +2. Create a new test file in your project's `test` directory. The file should have the same name as the file you want to test, with `_test` appended to the end. For example, if you want to test a file called `my_widget.dart`, the test file should be called `my_widget_test.dart`. + +3. Write test cases for the functions, widgets, or other parts of your application that you want to test. Use the tools provided by the flutter_test package, such as the test() and expect() functions, to define your test cases. + +4. Run your tests using the flutter test command. This will run all the tests in your project's test directory. + +## Basic Test Example + +Suppose you have a `Calculator` class with a `add` method that takes two integers and returns their sum: + +```dart +class Calculator { + int add(int a, int b) { + return a + b; + } +} +``` +To write a test for the `add` method, you can create a new file called `calculator_test.dart` in the same directory as your `calculator.dart` file, and write the following code: + +```dart +import 'package:flutter_test/flutter_test.dart'; +import 'package:my_app/calculator.dart'; + +void main() { + test('Calculator add method', () { + final calculator = Calculator(); + final result = calculator.add(2, 3); + expect(result, 5); + }); +} +``` +This test imports the `flutter_test` package, which provides the test function for writing tests, and imports the `Calculator` class from `calculator.dart`. The test function takes a string description of the test (in this case, "Calculator add method"), and a closure that contains the actual test code. + +Inside the closure, we create a new instance of the `Calculator` class, call its `add` method with the arguments 2 and 3, and store the result in a variable called `result`. We then use the expect function to `assert` that the value of `result` is equal to `5`. + +To run this test, you can run the following command in your terminal: + +```bash +flutter test +``` + +This will run all the tests in your project, including the `Calculator add` method test we just wrote. If the test passes, you should see the following output in your terminal: + +```bash +00:00 +1: All tests passed! +``` +If the test fails (for example, if the add method in calculator.dart was implemented incorrectly), you will see an error message in your terminal indicating what went wrong. + +## Intermediary Test Examples + +Now moving towards a more complex example where we will see the use of mocks and stubs to generate relevant tests for our code. First we will see what are mocks and stubs and how to use them. + + +### Mocks + +Mocks are objects that simulate the behavior of real objects in your application. They are often used in testing to isolate the part of your code that you want to test from the rest of the application. + +In Flutter, mocks can be generated using tools like Mockito, which is a popular mock object library for Dart. +To generate a mock object using Mockito, you can follow these steps: + 1. Add the `mockito` package to your pubspec.yaml file. + 2. Import the mockito package in your test file: + + ```dart + import 'package:mockito/mockito.dart'; + ``` + 3. Define a mock object by extending the Mock class: + + ```dart + class MockMyObject extends Mock implements MyObject {} + ``` + 4. Use the mock object in your test cases: + + ```dart + class MockMyObject extends Mock implements MyObject {} + ``` + + +### Mocks Test Example + + Suppose you have a `Calculator` class that performs arithmetic operations, and you want to test a `CalculatorController` class that uses the Calculator to perform + calculations. To isolate the `CalculatorController` for testing, you can create a mock `Calculator` object that simulates the behavior of the real Calculator. + + First, you'll need to create a `mock` object using `mockito` as stated above, which provides tools for creating mock objects. Here's an example of how to create a + mock + + ```dart + import 'package:mockito/mockito.dart'; + class MockCalculator extends Mock implements Calculator {} + // Create the mock object in your test case + final calculator = MockCalculator(); + ``` + + Now, you can use the `calculator` mock object to simulate the behavior of the `Calculator` in your tests. For example, here's a test that verifies that the + `CalculatorController` correctly adds two numbers: + + ```dart + test('CalculatorController adds two numbers', () { + // Create a new CalculatorController, passing in the mock Calculator + final controller = CalculatorController(calculator); + + // Stub the add method on the mock calculator + when(calculator.add(2, 3)).thenReturn(5); + + // Call the addNumbers method on the controller + final result = controller.addNumbers(2, 3); + + // Verify that the add method was called on the calculator + verify(calculator.add(2, 3)).called(1); + + // Verify that the result returned by the controller is correct + expect(result, equals(5)); +}); + ``` + + In this test, the `MockCalculator` is created and passed to the `CalculatorController`. The `when` method is used to stub the `add` method on the mock calculator, so + that when the `add` method is called with arguments 2 and 3, it returns the value 5. Then, the `addNumbers` method is called on the `CalculatorController`, and the + result is verified using the `expect` method. Finally, the `verify` method is used to ensure that the `add` method was called on the mock calculator with the correct + arguments. + + +### Stubs + +Stubbing is a technique used in testing to replace a real object with a simplified version that provides predictable behavior. + +In Flutter, you can use stubs to replace real objects with mock objects or other simplified versions. + +To stub a method or class in Flutter, you can use the when() function provided by the `mockito` package. For example, if you have a method called myMethod() that you want to stub, you can do the following: + +```dart +var myMock = MockMyObject(); +when(myMock.myMethod()).thenReturn('my result'); +``` +This will replace the `myMethod()` method on the `myMock` object with a stub that always returns the string `my result`. + +`when()` method is used in the previous Calculator example as well where it is used to stub the add method to mock the Calculator. + +You can also use the any matcher to match any input value. For example: + +```dart +when(myMock.myMethod(any)).thenReturn('my result'); +``` +This will stub the `myMethod()` method to always return `my result`, regardless of the input value. + +### Mocks and Stubs Test Example + +In this example, we'll be testing the `sendMessageToDirectChat` method from our application. This method is responsible for sending direct messages between two users in a private chat. The `sendMessageToDirectChat` method is critical to the functionality of our application, and we need to ensure that it works correctly under a variety of conditions. To do so, we'll be using a combination of manual and automated testing techniques to thoroughly test this method and uncover any potential bugs or issues. By the end of this example, you'll have a better understanding of how to approach testing for critical methods in this application. The file is located in `talawa/lib/services/chat_service.dart` and its tests are written in the file `talawa\test\service_tests\chat_service_test.dart` + +#### Method Under Test + +`sendMessageToDirectChat` is the function that sends a message of a person in his/her desired chat. Below is the code of this method which is to be tested if its functioning properly or not. + +```dart +Future sendMessageToDirectChat( + String chatId, + String messageContent, + ) async { + // trigger graphQL mutation to push the message in the Database. + final result = await _dbFunctions.gqlAuthMutation( + ChatQueries().sendMessageToDirectChat(), + variables: {"chatId": chatId, "messageContent": messageContent}, + ); + + final message = ChatMessage.fromJson( + result.data['sendMessageToDirectChat'] as Map, + ); + + _chatMessageController.add(message); + + debugPrint(result.data.toString()); + } +``` + +#### Sample Mock and Test Code + +Test written for this method looks like this + +```dart +test('Test SendMessageToDirectChat Method', () async { + final dataBaseMutationFunctions = locator(); + const id = "1"; + const messageContent = "test"; + + final query = ChatQueries().sendMessageToDirectChat(); + when( + dataBaseMutationFunctions.gqlAuthMutation( + query, + variables: { + "chatId": id, + "messageContent": messageContent, + }, + ), + ).thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(query)), + data: { + 'sendMessageToDirectChat': { + '_id': id, + 'messageContent': messageContent, + 'sender': { + 'firstName': 'Mohamed', + }, + 'receiver': { + 'firstName': 'Ali', + } + }, + }, + source: QueryResultSource.network, + ), + ); + final service = ChatService(); + await service.sendMessageToDirectChat( + id, + messageContent, + ); + }) +``` + +#### Test Explanation + +Here is a breakdown of what this test does + +1. The test starts by defining a mock object for the `_dbFunctions` class using the when function from the `Mockito` package. The mock object is set up to return a `QueryResult` object that simulates the result of a GraphQL mutation when the `gqlAuthMutation` method is called with the correct query and variables. + +2. The `ChatService` class is instantiated, and the `sendMessageToDirectChat` method is called with the correct `chatId` and `messageContent` parameters. + +3. Finally, the test verifies that the `_chatMessageController` object has been updated with the correct `ChatMessage` object that was received from the mocked `GraphQL mutation` result. + +4. The `when` function is used to set up a mock behavior for the `gqlAuthMutation` method of the `_dbFunctions` object. The mocked behavior returns a `QueryResult` object that simulates the result of a GraphQL mutation. The `QueryResult` object contains a map with a key of `sendMessageToDirectChat`, which contains a value that represents the returned `ChatMessage` object from the mutation. + +Overall, this test verifies that the `sendMessageToDirectChat` method correctly triggers a GraphQL mutation and correctly handles the returned data by updating the `_chatMessageController` object with the expected `ChatMessage` object. + + +## Troubleshooting + +If you find a bug while writing a test for a file, the first thing to do is to write a test case that reproduces the bug. This will help you ensure that the bug is fixed and doesn't reappear in the future. + +Once you have a failing test case, you should debug the code and identify the cause of the bug. You can use tools like the print() function, the debugger in your IDE, or the debugPrint() function provided by Flutter to help you debug your code. + +Once you have identified the cause of the bug, you should fix the code and run your tests again to ensure that the bug has been fixed. If you have multiple test cases that cover the same code, you should run all of them to ensure that the fix doesn't break any other parts of your code. + +If you are working in a team, it's a good idea to communicate the bug and the fix to your teammates so that they are aware of the issue and can review your fix. + + +## Conclusion + +Writing tests is an important part of the software development process, and Flutter provides a set of tools that make it easy to write tests for your application. By following the guidelines in this guide, you can ensure that your tests cover all corner cases, use mocks and stubs to isolate your code, and effectively debug and fix bugs that you encounter during the testing process. diff --git a/docs/docs/docs/introduction.md b/docs/docs/docs/introduction.md new file mode 100644 index 000000000..8adb566a3 --- /dev/null +++ b/docs/docs/docs/introduction.md @@ -0,0 +1,9 @@ +--- +id: introduction +title: Overview +slug: / +--- + +## Talawa + +Talawa is a modular open source project to manage group activities of both non-profit organizations and businesses. diff --git a/docs/docs/docs/offline/action-handler-service.md b/docs/docs/docs/offline/action-handler-service.md new file mode 100644 index 000000000..4555c1faf --- /dev/null +++ b/docs/docs/docs/offline/action-handler-service.md @@ -0,0 +1,240 @@ +--- +id: action-handler-service +title: ActionHandlerService +--- + +The **ActionHandlerService** provides a standardized way to handle both optimistic and critical user actions within the application, ensuring that they are executed consistently, regardless of whether the user is online or offline. This documentation outlines the flow of the `performAction` method, with detailed step-by-step explanations for both optimistic and critical actions. + +The service focuses on: + +- **Optimistic actions**: These actions immediately update the UI, assuming that the API call will succeed. +- **Critical actions**: These actions wait for the API call to succeed before updating the UI. They are important actions that require confirmation from the server. + +## Key Features: + +- Handles API calls with extensive error handling and optional UI updates. +- Supports optimistic and critical actions with distinct UI behaviors. +- Provides hooks for handling valid results, exceptions, and final tasks after API execution. + +--- + +## **ActionHandlerService PerformAction Flow** + +![ActionHandler PerformAction Flow](../../../static/img/markdown/offline/perform_action_wrapper_flow.png) + +### 1. User Initiates an Action + +The user triggers an action in the app, such as liking a post, commenting, or creating/deleting an event. Each action is routed through the **performAction** method of the `ActionHandlerService`, which wraps the action with logic to handle network status, UI updates, and error management. + +--- + +### 2. Wrapping Actions with ActionHandlerService + +The `performAction` method serves as a central point for managing all user actions. It handles: + +- UI updates for optimistic actions. +- Network state detection. +- Executing API calls asynchronously. +- Offline behavior management, including retries for actions. + +--- + +### 3. Action Type Decision + +The `performAction` method categorizes actions as either **optimistic** or **critical**: + +- **Optimistic Actions**: These actions (e.g., liking or commenting on a post) allow immediate UI feedback to the user before confirmation from the server. +- **Critical Actions**: These actions (e.g., creating or deleting a post) require reliable server interaction and will only update the UI after the server response. + +--- + +### 4. Handling Optimistic Actions + +#### i. UI Update Prior to API Call + +For optimistic actions, the UI is updated immediately after the action is performed, giving the user instant feedback. For example, when a user likes a post, the like button changes to the "liked" state. + +#### ii. Execute API Call + +After the UI update, the `executeApiCall` method is called asynchronously to send the action request to the server. + +#### iii. Post-API Behavior + +- **Success Case**: If the API call succeeds, the UI is already updated, so no further action is required. +- **Failure Case**: If the API call fails (e.g., the user is offline), the user is notified that the action will be performed once the device is back online. + +#### Error Handling for Optimistic Actions + +In case of failure, optimistic actions should have robust error handling to notify the user and queue the action for later execution. + +--- + +### 5. Handling Critical Actions + +#### i. Check Network Status + +The network status is checked before executing critical actions to determine whether the user is online or offline. + +#### ia. Online Status + +- **Execute API Call**: If the device is online, the `executeApiCall` method sends the request to the server. +- **Post-API UI Update**: Once the server confirms the action, the UI is updated accordingly (e.g., the new post appears in the feed). +- **ApiCallSuccess Callback**: After the API call completes, additional tasks like notifications or navigation are triggered via the success callback. + +#### ib. Offline Status + +If the device is offline, the user is notified, and no API call is made. The user is asked to retry once the device is connected to the internet. + +--- + +### 6. The `executeApiCall` Method + +The `executeApiCall` method is responsible for: + +- **Asynchronous Network Request**: Sending the API request without blocking the UI. +- **Error Handling**: Managing network failures or server errors by informing the user or retrying the action. +- **Success Callback**: Triggering additional tasks after the API call completes successfully. + +--- + +### 7. Optimizing for Offline-First Design + +The `performAction` method is designed to accommodate offline scenarios: + +- **Optimistic Actions**: The UI is updated before the server response to provide a seamless user experience. +- **Critical Actions**: API calls are only made when the device is online, ensuring data integrity. + +--- + +### 8. Summary of Key Flows + +- **Optimistic Actions**: + - Immediate UI update followed by API call. + - Error handling for failed API calls. +- **Critical Actions**: + - Network status is checked before performing the action. + - Online: API call, followed by UI update and success callback. + - Offline: User is notified, and no action is taken. + +--- + +### Methods: + +#### `executeApiCall` + +Executes an API action with appropriate error handling. + +##### Parameters: + +- `action`: A function that performs the API call and returns a `Future?>`. +- `onValidResult`: A callback function that processes the API call result when it is successful. +- `onActionException`: A callback function that handles any exceptions that occur during the API call. +- `onActionFinally`: A callback function that executes regardless of the success or failure of the API call (typically used for cleanup tasks). + +##### Returns: + +- `Future`: A boolean indicating the success (`true`), failure (`false`), or `null` if the result is invalid. + +#### Usage Example: + +```dart +final result = await actionHandlerService.executeApiCall( + action: () async => await yourGraphqlClient.query(options), + onValidResult: (result) async { + // Handle valid result + }, + onActionException: (e) async { + // Handle exception + }, + onActionFinally: () async { + // Perform final cleanup or update UI + }, +); +``` + +### `performAction` + +Executes a user action based on its type (`optimistic` or `critical`), updating the UI and handling errors accordingly. + +#### Parameters: + +- `actionType`: Specifies whether the action is optimistic or critical. (`ActionType.optimistic` or `ActionType.critical`). +- `action`: The action to perform, which returns a `Future?>`. +- `onValidResult`: A callback function that processes the result when the action is successful. +- `onActionException`: A callback function that handles exceptions during the action. +- `updateUI`: A callback function to update the UI. For optimistic actions, it runs immediately; for critical actions, it runs after the API call. +- `apiCallSuccessUpdateUI`: A callback function to update the UI after a successful API call. +- `criticalActionFailureMessage`: A string for the error message used when a critical action fails. +- `onActionFinally`: A callback function to execute regardless of the success or failure of the action. + +#### Returns: + +- `Future` + +#### Usage Example: + +```dart +await actionHandlerService.performAction( + actionType: ActionType.optimistic, + action: () async => await yourGraphqlClient.query(options), + onValidResult: (result) async { + // Handle valid result + }, + onActionException: (e) async { + // Handle exception + }, + updateUI: () { + // Immediately update UI for optimistic action + }, + apiCallSuccessUpdateUI: () { + // Update UI after API call success + }, + criticalActionFailureMessage: 'Failed to save your action. Please try again.', + onActionFinally: () async { + // Perform final cleanup + }, +); +``` + +## Optimistic vs. Critical Actions + +- **Optimistic actions** immediately update the UI before the API call is made. This improves perceived responsiveness, but the UI may need to be rolled back if the API call fails. +- **Critical actions** wait for a response from the server before updating the UI. This ensures that the UI reflects the actual state after the server confirms the action. + +--- + +## Error Handling + +The `ActionHandlerService` is designed to gracefully handle exceptions. For optimistic actions, the UI is updated before the API call, but if an error occurs, a rollback or UI adjustment might be needed. For critical actions, error messages are handled based on the type of error, and the UI is updated accordingly. + +In case the device is offline, a **critical action** will trigger an exception with a user-friendly message, notifying the user that their action was not saved. + +#### Example: + +```dart +if (await connectivityViewModel.isOnline()) { + await actionHandlerService.performAction( + actionType: ActionType.critical, + action: () async => await yourGraphqlClient.mutate(options), + onValidResult: (result) async { + // Handle valid mutation result + }, + onActionException: (e) async { + // Handle exception, such as showing an error message to the user + }, + updateUI: () { + // Update UI only after the API call for critical actions + }, + apiCallSuccessUpdateUI: () { + // Additional UI update on successful API call + }, + criticalActionFailureMessage: 'Failed to save critical action. Please try again.', + onActionFinally: () async { + // Perform any final tasks after the API call + }, + ); +} else { + // Handle the case where the device is offline + showOfflineError(); +} +``` diff --git a/docs/docs/docs/offline/feed-caching.md b/docs/docs/docs/offline/feed-caching.md new file mode 100644 index 000000000..9805a43bf --- /dev/null +++ b/docs/docs/docs/offline/feed-caching.md @@ -0,0 +1,159 @@ +--- +id: feed-caching +title: Feed Caching +--- + +An abstract base class for managing a feed of type `[T]` with support for caching and online data fetching. This class is designed to provide a unified interface for handling data operations that involve both local caching and remote API interactions. + +### Overview + +`BaseFeedManager` provides a robust framework for managing data feeds by: + +- Initializing a cache using Hive. +- Loading data from the local cache. +- Saving data to the local cache. +- Fetching new data from a remote API. +- Automatically refreshing the cache based on online connectivity. + +This class is generic and can be used with any data type by specifying the type parameter `[T]`. Subclasses must implement the abstract method `fetchDataFromApi` to define how to retrieve data from an API. + +### Flow-breakdown + +![Basefeed manager](../../../static/img/markdown/offline/perform_action_wrapper_flow.png) + +1. **Initialize Feed** + + - The `BaseFeedManager` is initialized with the specific feed type. During initialization: + - The associated Hive box for caching is set up. + - Any previously cached feed data is loaded from local storage. + +2. **Serve Cached Data** + + - The feed manager immediately retrieves and serves cached data from local storage to the UI. + - This ensures that users see previously loaded content while new data is fetched in the background. + +3. **Fetch Fresh Data** + + - The API Service performs a network request to fetch the latest feed data from the server. + - This operation occurs asynchronously to avoid blocking the UI thread. + +4. **Update Cache** + + - Once fresh data is successfully fetched, it is stored in the cache. + - The existing cache is cleared and replaced with the new data to ensure that the cache remains up-to-date. + +5. **UI Update** + - The UI is updated to display the latest feed content. + - This involves notifying relevant components to refresh and render the updated data, ensuring that users have access to the most recent information. + +### Constructor + +#### `BaseFeedManager(String cacheKey)` + +Initializes the `BaseFeedManager` with a unique cache key for Hive. + +**Parameters:** + +- `cacheKey`: A unique string used to identify the cache box in Hive. + +### Properties + +- **`String cacheKey`**: The unique key used to access the Hive cache box. +- **`Box _box`**: The Hive box that stores the cached data. + +### Methods + +**Method** + +#### `_initialize()` + +Initializes the Hive box associated with the `cacheKey`. + +**Returns:** + +- `Future`: Completes when the initialization is finished. + +**Method** + +#### `Future> loadCachedData()` + +Loads the data cached in Hive. + +**Returns:** + +- `Future>`: A `Future` that resolves to a list of cached data of type `[T]`. + +**Method** + +#### `Future saveDataToCache(List data)` + +Saves a list of data to the cache, replacing any existing data. + +**Parameters:** + +- `data`: A list of data of type `[T]` to be saved in the cache. + +**Returns:** + +- `Future`: Completes when the data has been saved to the cache. + +**Method** + +#### `Future> fetchDataFromApi()` + +An abstract method that must be implemented by subclasses to fetch data from an API. + +**Returns:** + +- `Future>`: A `Future` that resolves to a list of data of type `[T]` fetched from the API. + +**Method** + +#### `Future> getNewFeedAndRefreshCache()` + +Fetches new data from the API if online, updates the cache, and returns the data. If offline, it loads and returns cached data. + +**Returns:** + +- `Future>`: A `Future` that resolves to a list of the latest data of type `[T]`. + +### Example Usage + +To use `BaseFeedManager`, create a subclass that implements the `fetchDataFromApi` method. Here is an example of how to create a concrete implementation: + +```dart +import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; +import 'base_feed_manager.dart'; // Import your BaseFeedManager class + +class MyFeedManager extends BaseFeedManager { + MyFeedManager() : super('myFeedCacheKey'); + + @override + Future> fetchDataFromApi() async { + // Replace with your actual API fetching logic + final response = await http.get(Uri.parse('https://api.example.com/data')); + if (response.statusCode == 200) { + final List jsonData = json.decode(response.body); + return jsonData.map((item) => MyDataType.fromJson(item)).toList(); + } else { + throw Exception('Failed to load data'); + } + } +} + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + // Open Hive box + await Hive.openBox('myFeedCacheKey'); + + final feedManager = MyFeedManager(); + + // Fetch and refresh cache + final data = await feedManager.getNewFeedAndRefreshCache(); + + // Use the data + print(data); +} +``` diff --git a/docs/docs/docs/offline/offline-user-actions.md b/docs/docs/docs/offline/offline-user-actions.md new file mode 100644 index 000000000..12734e3ed --- /dev/null +++ b/docs/docs/docs/offline/offline-user-actions.md @@ -0,0 +1,182 @@ +--- +id: offline-user-actions +title: Offline User Actions +--- + +This document outlines the detailed process for managing user actions with GraphQL operations in scenarios where network connectivity can vary. The flow includes creating GraphQL operations, determining whether to execute or cache these operations based on network status, and handling offline scenarios by queuing actions and executing them once the device is back online. + +## Flow-Breakdown + +![High level offline action flow](../../../static/img/markdown/offline/High_level_offline_action_flow.png) + +1. **User Action Initiation** + + - The flow begins when a user performs an action within the application. This action could involve interacting with UI elements, such as liking a post, creating an event, or any other activity that requires communication with a backend server. + +2. **GraphQL Operation Creation** + + - Based on the user action, a GraphQL operation is created. This operation represents the request to be sent to the server to process the user’s action. The operation could be a mutation or query depending on the action performed. + +3. **Determine Execution or Caching** + + - The `executeOrCacheOperation()` method of the `cacheService` is called to decide whether to execute the GraphQL operation immediately or cache it for later execution based on the device's network connectivity. + + - **3a. If the Device is Online:** + + - **i. Execute GraphQL Operation:** + - The GraphQL operation is executed immediately. The `cacheService` sends the request to the server, and the response is processed accordingly. This ensures real-time processing and immediate feedback to the user. + + - **3b. If the Device is Offline:** + + - **i. Create Cached User Action:** + + - A `cachedUserAction` is created to represent the offline GraphQL operation. This object includes all necessary details of the operation, such as the operation type, variables, and any metadata required for execution. + + - **ii. Push to Offline Action Queue:** + + - The `cachedUserAction` is pushed to the `OfflineActionQueue`, a data structure designed to store actions that cannot be executed immediately due to the lack of network connectivity. + + - **iii. Wait Until Device is Back Online:** + + - The system continuously monitors network connectivity and waits until the device regains online status before proceeding to execute any actions in the `OfflineActionQueue`. + + - **iv. Process Offline Actions:** + + - When the device is back online, the application pops each action from the `OfflineActionQueue` and processes them. Actions are executed in the order they were added to the queue. + + - **v. Re-execute Actions:** + - For each `cachedUserAction`, the system recreates the GraphQL operation and executes it as it was intended initially. This ensures that all user actions are processed, even if there were temporary connectivity issues. + +## CachedUserAction Model Documentation + +The `CachedUserAction` class represents a user action that is cached for offline use. This model is used to store actions when the device is offline, allowing them to be executed once the device is back online. This documentation covers the class's properties, methods, and usage. + +### Overview + +The `CachedUserAction` class provides the following functionalities: + +- **Serialization:** Converts the object to and from JSON for easy storage and retrieval. +- **Execution:** Executes the cached user action based on its type, leveraging GraphQL operations. + +### Properties + +#### `id` + +- **Type:** `String` +- **Description:** The unique identifier for the cached user action. +- **Hive Field:** `0` + +#### `operation` + +- **Type:** `String` +- **Description:** The GraphQL operation to be performed for the cached user action. This could be a mutation or a query. +- **Hive Field:** `1` + +#### `variables` + +- **Type:** `Map?` +- **Description:** The variables required for the GraphQL operation, if any. +- **Hive Field:** `2` + +#### `timeStamp` + +- **Type:** `DateTime` +- **Description:** The timestamp when the action was cached. This helps in managing the order of operations and expiration of cache. +- **Hive Field:** `3` + +#### `status` + +- **Type:** `CachedUserActionStatus` +- **Description:** The status of the cached user action, which indicates whether the action is pending, completed, or failed. +- **Hive Field:** `4` + +#### `metaData` + +- **Type:** `Map?` +- **Description:** Any additional metadata related to the cached user action. This could include information about the context or additional details relevant to the operation. +- **Hive Field:** `5` + +#### `operationType` + +- **Type:** `CachedOperationType` +- **Description:** The type of operation for the cached user action. This helps in determining how the operation should be executed (e.g., authenticated vs. non-authenticated queries or mutations). +- **Hive Field:** `6` + +#### `expiry` + +- **Type:** `DateTime` +- **Description:** The expiry date and time for the cached user action. Actions that have expired are generally not executed and may be purged from the cache. +- **Hive Field:** `7` + +### Methods + +#### `fromJson` + +- **Description:** Creates a `CachedUserAction` instance from a JSON-compatible map. +- **Parameters:** + - `json` (`Map`): A map representing the `CachedUserAction`. +- **Returns:** + - `CachedUserAction`: A new instance of `CachedUserAction`. + +#### `toJson` + +- **Description:** Converts the `CachedUserAction` to a JSON-compatible map. +- **Parameters:** + - None +- **Returns:** + - `Map`: A map representing the `CachedUserAction`. + +#### `toString` + +- **Description:** Returns a string representation of the `CachedUserAction` instance. +- **Parameters:** + - None +- **Returns:** + - `String`: A string representation of the `CachedUserAction`. + +#### `execute` + +- **Description:** Executes the cached user action based on its operation type. +- **Parameters:** + - None +- **Returns:** + - `Future>`: The result of the executed GraphQL operation. The result is wrapped in a `QueryResult` object, which includes details of the operation's success or failure. + +#### Operation Types Handled + +The `execute` method handles various GraphQL operations based on the `operationType`: + +- **`gqlAuthQuery`:** Executes an authenticated GraphQL query. +- **`gqlAuthMutation`:** Executes an authenticated GraphQL mutation. +- **`gqlNonAuthQuery`:** Executes a non-authenticated GraphQL query. +- **`gqlNonAuthMutation`:** Executes a non-authenticated GraphQL mutation. +- **Default Case:** Returns `databaseFunctions.noData` if the operation type is unknown. + +### Usage + +The `CachedUserAction` class is used in scenarios where user actions need to be cached due to offline conditions. It helps ensure that user actions are not lost and can be executed later when the device is back online. The class supports both serialization for persistence and execution based on operation type. + +#### Example Usage + +```dart +// Creating a CachedUserAction +final action = CachedUserAction( + id: 'unique-id', + operation: 'mutation LikePost($postId: ID!) { likePost(postId: $postId) { id likes } }', + variables: {'postId': '123'}, + timeStamp: DateTime.now(), + status: CachedUserActionStatus.pending, + metaData: {'source': 'user_action'}, + operationType: CachedOperationType.gqlAuthMutation, + expiry: DateTime.now().add(Duration(days: 1)), +); + +// Converting to JSON +final json = action.toJson(); + +// Creating from JSON +final newAction = CachedUserAction.fromJson(json); + +// Executing the action +final result = await newAction.execute(); +``` diff --git a/docs/docs/docs/talawa-lint.md b/docs/docs/docs/talawa-lint.md new file mode 100644 index 000000000..9c844d505 --- /dev/null +++ b/docs/docs/docs/talawa-lint.md @@ -0,0 +1,205 @@ +--- +id: talawa-lint +title: Talawa Lint Ecosystem +--- + +## Talawa Lint + +`talawa_lint` is Talawa's custom lint rule set that we enforced to ensure +proper documentation for our codebase. It is a mini package that lives as a +sub-directory in `talawa` package. + +### Proper Installation + +Since `talawa_lint` is a package in itself, it has its own dependencies which +should be installed. To do so, go to `talawa` directory, then: + +```bash +cd talawa_lint +flutter pub get +cd .. +flutter pub get +``` + +This will install all the required dependencies properly. +Sometimes (highly unlikely), you may get `Conflicting plugin version` error. In that +case you will need to clean your dev environment of `talawa` and reinstall packages. +To do so, go to `talawa` directory, then: + +```bash +flutter clean +cd talawa_lint +flutter clean +flutter pub get +cd .. +flutter pub get +``` + +This should resolve the conflicting issues. + +### Usage + +If the installation was successful, you will be able to get lint warnings/errors right +in your IDE, as well as analyze your files from command line. + +#### In IDE + +With proper installation, you will get lint warnings if either your fields don't have any +documentation, or the documentation is not in the right format. Something like this + +![Lint warning example](../../static/img/markdown/talawa_lint/lint_warning_ex.png) + +#### Command line + +Run `flutter pub run custom_lint` in `talawa` root directory to analyze all of the files +for custom lint rules. + +#### Logs + +Logs are generated for our custom lint analysis in the file named `custom_lint.log`. This file +exists under both `talawa` and `talawa_lint` directories. You can find any error related to +`talawa_lint` integration in these logs. This comes handy while troubleshooting. + +## Talawa Lint Rules + +For now, we enforce two custom lint rules to our codebase, which should be strictly followed +by each file and their fields (classes, methods, attributes, getters, setters, top level variables, +functions, enums.......).

+The rules being. + +1. `talawa_api_doc` +2. `talawa_good_doc_comments` + +### talawa_api_doc + +This lint rule checks for presence of documentation comment for any field. You will get a warning +as shown here + +![No Lint warning example](../../static/img/markdown/talawa_lint/no_lint_ex.png) + +Note that a documentation comment is different from a normal comment as in normal comment starts +with `//` whereas doc comment starts with `///`. + +### talawa_good_doc_comments + +This is where the fun lies. This lint rule checks if the documentation provided for a field is in +the format that we expect. The format being: + +#### For non function/method/getter/setter (classes, enums, variables etc) + +1. First line of documentation must be a complete line ending with end punctuation (`.`, `!`, `?`) +2. If you think that there should be more to the documentation, leave the second line empty and write + further documentation in paragraph fashion from the next line. + +Examples of valid documentation include: + +##### Single line + +```js +/// This is my class and it does stuff. +class MyClass {} +``` + +##### Multi line + +```js +/// This is my class and it does stuff. +/// +/// The stuff includes playing pong with +/// my chip-8 emulator. +class MyClass {} +``` + +#### For functions and methods + +Same rules as for non functions/methods as described above, in conjunction with two other +sections for `**params**:` and `**returns**:` + +1. Follow same rules as described in the above section. +2. Add `/// **params**:` block. This marks the beginning of `params` section where you describe all the parameters + that this function takes from the next line. The format being + - `` /// * `param_name`: `` followed by its description starting in the same line. + - The description can be multi line and requires no extra formatting. + - If the function takes no parameters, `/// **params**:` should be followed by `/// None` in the next line. +3. After documenting all of the parameters, add `/// **returns**:` block. Note that there must be a blank line `///` between + the `**params**` and `**returns**` blocks. +4. Followed by returns: + - Add `` /// * `return_type`: `` followed by its description starting in the same line. + - The description can be multi line and requires no extra formatting. + - If the function returns void, `/// **returns**:` should be followed by `/// None` in the next line. + Note that `Future` is different from void. `/// None` is strictly used only for `void` type. + +Examples of valid documentation include: + +##### No parameter and void return + +```js +/// My fun. +/// +/// Other description. +/// +/// **params**: +/// None +/// +/// **returns**: +/// None +void fun () {} +``` + +##### Has parameter(s) and void return + +```js +/// My fun. +/// +/// Other description. +/// +/// **params**: +/// * `name`: description +/// * `age`: description +/// +/// **returns**: +/// None +void fun (String? name, int age) {} +``` + +##### No parameter and non-void return + +```js +/// My fun. +/// +/// Other description. +/// +/// **params**: +/// None +/// +/// **returns**: +/// * `int`: Answer of life. +int fun () { + return 42; +} +``` + +##### Has parameter(s) and non-void return + +```js +/// My fun. +/// +/// Other description. +/// +/// **params**: +/// * `name`: description +/// * `age`: description +/// +/// **returns**: +/// * `int`: Answer of life. +int fun (String? name, int age) { + return 42; +} +``` + +:::note + +1. The `params:` block expects the **name** of the parameter and **not its type**. +2. The `returns:` block expects the **type** of the parameter and **not its name**. + +::: diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts index 575be41b5..38b05f28b 100644 --- a/docs/docusaurus.config.ts +++ b/docs/docusaurus.config.ts @@ -1,108 +1,116 @@ -import { themes as prismThemes } from 'prism-react-renderer'; -import type { Config } from '@docusaurus/types'; -import type * as Preset from '@docusaurus/preset-classic'; +import { themes as prismThemes } from "prism-react-renderer"; +import type { Config } from "@docusaurus/types"; +import type * as Preset from "@docusaurus/preset-classic"; // This runs in Node.js - Don't use client-side code here (browser APIs, JSX...) const config: Config = { - title: 'Talawa Mobile Documentation', - tagline: 'Start your open source journey here', - favicon: 'img/favicon.ico', + title: "Talawa Mobile Documentation", + tagline: "Start your open source journey here", + favicon: "img/favicon.ico", - url: 'https://docs-mobile.talawa.io', - baseUrl: '/', - deploymentBranch: 'gh-pages', + url: "https://docs-mobile.talawa.io", + baseUrl: "/", + deploymentBranch: "gh-pages", - organizationName: 'PalisadoesFoundation', // GitHub org - projectName: 'talawa', // repo name + organizationName: "PalisadoesFoundation", // GitHub org + projectName: "talawa", // repo name - onBrokenLinks: 'throw', - onBrokenMarkdownLinks: 'warn', + onBrokenLinks: "throw", + onBrokenMarkdownLinks: "warn", // Even if you don't use internationalization, you can use this field to set // useful metadata like html lang. For example, if your site is Chinese, you // may want to replace "en" with "zh-Hans". i18n: { - defaultLocale: 'en', - locales: ['en'], + defaultLocale: "en", + locales: ["en"], }, presets: [ [ - 'classic', + "classic", + /** @type {import('@docusaurus/preset-classic').Options} */ { docs: { - sidebarPath: './sidebars.ts', - // Please change this to your repo. - // Remove this to remove the "edit this page" links. + sidebarPath: require.resolve("./sidebars.js"), + editUrl: ({ docPath }) => { + return `https://github.com/PalisadoesFoundation/talawa/edit/develop/docs/docs/${docPath}`; + }, + }, + blog: { + showReadingTime: true, editUrl: - 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/', + "https://github.com/PalisadoesFoundation/talawa/tree/develop/docs/docs", }, theme: { - customCss: './src/css/custom.css', + customCss: [ + require.resolve("./src/css/custom.css"), + require.resolve("./src/css/index.css"), + ], }, - } satisfies Preset.Options, + }, ], ], themeConfig: { // Replace with your project's social card - image: 'img/docusaurus-social-card.jpg', + image: "img/docusaurus-social-card.jpg", navbar: { - title: 'My Site', + title: "Talawa Mobile", logo: { - alt: 'My Site Logo', - src: 'img/logo.svg', + alt: "My Site Logo", + src: "img/logo.svg", }, items: [ { - type: 'docSidebar', - sidebarId: 'tutorialSidebar', - position: 'left', - label: 'Tutorial', + type: "docSidebar", + sidebarId: "tutorialSidebar", + position: "left", + label: "Docs", }, { - href: 'https://github.com/facebook/docusaurus', - label: 'GitHub', - position: 'right', + href: "https://github.com/facebook/docusaurus", + label: "GitHub", + position: "right", }, ], }, footer: { - style: 'dark', + style: "dark", links: [ { - title: 'Docs', + title: "Docs", items: [ { - label: 'Tutorial', - to: '/docs/intro', + label: "Docs", + to: "/docs", }, ], }, { - title: 'Community', + title: "Community", items: [ { - label: 'Stack Overflow', - href: 'https://stackoverflow.com/questions/tagged/docusaurus', + label: "Stack Overflow", + href: "https://stackoverflow.com/questions/tagged/docusaurus", }, { - label: 'Discord', - href: 'https://discordapp.com/invite/docusaurus', + label: "Discord", + href: "https://discordapp.com/invite/docusaurus", }, { - label: 'X', - href: 'https://x.com/docusaurus', + label: "X", + href: "https://x.com/docusaurus", }, ], }, { - title: 'More', + title: "More", items: [ { - label: 'GitHub', - href: 'https://github.com/facebook/docusaurus', + label: "GitHub", + href: "https://github.com/facebook/docusaurus", }, ], }, diff --git a/docs/sidebars.ts b/docs/sidebars.ts index 68d3ae97b..c163f324a 100644 --- a/docs/sidebars.ts +++ b/docs/sidebars.ts @@ -1,4 +1,4 @@ -import type { SidebarsConfig } from '@docusaurus/plugin-content-docs'; +import type { SidebarsConfig } from "@docusaurus/plugin-content-docs"; // This runs in Node.js - Don't use client-side code here (browser APIs, JSX...) @@ -14,7 +14,21 @@ import type { SidebarsConfig } from '@docusaurus/plugin-content-docs'; */ const sidebars: SidebarsConfig = { // By default, Docusaurus generates a sidebar from the docs folder structure - tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }], + tutorialSidebar: [ + "docs/introduction", + "docs/talawa-lint", + "docs/flutter-testing", + { + type: "category", + label: "Offline Features", + items: [{ type: "autogenerated", dirName: "docs/offline" }], + }, + { + type: "category", + label: "Code Documentation", + items: [{ type: "autogenerated", dirName: "auto-docs" }], + }, + ], // But you can create a sidebar manually /* diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css index 2bc6a4cfd..681c0404f 100644 --- a/docs/src/css/custom.css +++ b/docs/src/css/custom.css @@ -2,29 +2,451 @@ * Any CSS included here will be global. The classic template * bundles Infima by default. Infima is a CSS framework designed to * work well for content-centric websites. + * You can override the default Infima variables here. */ -/* You can override the default Infima variables here. */ :root { - --ifm-color-primary: #2e8555; - --ifm-color-primary-dark: #29784c; - --ifm-color-primary-darker: #277148; - --ifm-color-primary-darkest: #205d3b; - --ifm-color-primary-light: #33925d; - --ifm-color-primary-lighter: #359962; - --ifm-color-primary-lightest: #3cad6e; + --h1-markdown: #021526; + --h2-markdown: #3a6d8c; + --h3-markdown: #474e93; + --h4-markdown: #508c9b; + --h5-markdown: #6a9ab0; + --h6-markdown: #888888; + --hx-markdown-underline: #eeeeee; + --secondary-blue-900: #001c63; + --sidebar-bg-color: #f3f4f6; + --secondary-blue-500: #3970fd; + --primary-blue-600: #1e56e3; + --base-neutral-0: #ffffff; + --primary-neutral-800: #1f2a37; + --ifm-menu-color-active: #1e56e3; + --primary-neutral-600: #4d5761; + --ifm-breadcrumb-color-active: var(--primary-neutral-600); + --ifm-link-color: #1e56e3; + --ifm-button-background-color: #2e8555; + --ifm-button-background-color-dark: #205d3b; + --ifm-hover-overlay: rgba(0, 0, 0, 0.05); + --brand-color: black; + --next-prev-border-color: #e5e7eb; + --ifm-color-emphasis-100: #f4f8fb; + --ifm-color-emphasis-0: #fff; + --ifm-color-primary: #1e56e3; + --ifm-background-surface-color: var(--ifm-color-white); + --ifm-menu-color: var(--ifm-color-gray-600); + --ifm-toc-link-color: var(--ifm-color-gray-600); --ifm-code-font-size: 95%; - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); -} - -/* For readability concerns, you should choose a lighter palette in dark mode. */ -[data-theme='dark'] { - --ifm-color-primary: #25c2a0; - --ifm-color-primary-dark: #21af90; - --ifm-color-primary-darker: #1fa588; - --ifm-color-primary-darkest: #1a8870; - --ifm-color-primary-light: #29d5b0; - --ifm-color-primary-lighter: #32d8b4; - --ifm-color-primary-lightest: #4fddbf; - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); + --ifm-toc-border-color: transparent; + --ifm-code-background: #e5ecff; + --ifm-code-color: #0087ff; + --ifm-color-content: #000e33; + --ifm-heading-line-height: 1.5; + --ifm-h1-font-size: 2.25rem; + --ifm-h2-font-size: 1.875rem; + --ifm-navbar-shadow: 0 1px 2px 0 #0000001a; + --ifm-navbar-search-input-background-color: var(--ifm-color-gray-100); + --ifm-navbar-search-input-color: var(--ifm-color-content); + --ifm-table-stripe-background: #efeff2; + --ifm-font-family-base: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, + Cantarell, Noto Sans, sans-serif, BlinkMacSystemFont, 'Segoe UI', Helvetica, + Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; + --ifm-font-family-monospace: 'IBM Plex Mono', SFMono-Regular, Menlo, Monaco, + Consolas, 'Liberation Mono', 'Courier New', monospace; +} + +/* Dak mode css */ + +html[data-theme='dark'] { + --ifm-background-color: #111927; + --ifm-background-surface-color: var(--ifm-background-color); + --ifm-menu-color: var(--ifm-color-gray-200); + --ifm-toc-link-color: var(--ifm-color-gray-200); + --ifm-code-background: #001b66; + --ifm-color-content: var(--ifm-color-white); + --ifm-navbar-search-input-background-color: #001b66; + --ifm-table-stripe-background: #001242; + --ifm-navbar-search-input-placeholder-color: var(--ifm-color-gray-200); + --ifm-navbar-search-input-icon: url('data:image/svg+xml;utf8,'); + + --ifm-hover-overlay: rgba(0, 0, 0, 0); + --ifm-color-primary: #1e56e3; + --secondary-blue-900: #c6d6ff; + --sidebar-bg-color: #161f36; + --primary-neutral-800: #c9c9cc; + --ifm-button-background-color: #25c2a0; + --ifm-button-background-color-dark: #2e8555; + --ifm-navbar-link-color: #9da4ae; + --brand-color: white; + --primary-neutral-600: #c4c4c4; + --next-prev-border-color: #293441; + --ifm-color-emphasis-100: #1d1e30; + --ifm-color-emphasis-0: #111f3b; +} + +.docusaurus-highlight-code-line { + background-color: rgb(72, 77, 91); + display: block; + margin: 0 calc(-1 * var(--ifm-pre-padding)); + padding: 0 var(--ifm-pre-padding); +} + +.table-of-contents { + font-size: 0.75rem; +} + +h1.docTitle_node_modules-\@docusaurus-theme-classic-src-theme-DocItem- { + font-size: var(--ifm-h1-font-size); + margin-bottom: 1.5rem; +} + +.menu { + background-color: var(--sidebar-bg-color); +} + +.menu__link, +.menu * { + line-height: 1.5; + font-size: 0.7rem; + padding-left: 0.5rem; + padding-bottom: 0; + text-transform: uppercase; + font-weight: 700; + background: transparent !important; +} + +.menu__list { + border-bottom: 1px solid var(--next-prev-border-color); +} + +.menu__list ul { + margin-left: 15px; + margin-right: 15px; +} + +.markdown > h2 { + --ifm-h2-font-size: 1.875rem; + margin-bottom: 0.8rem; + margin-top: calc(var(--ifm-h2-vertical-rhythm-top) * 0rem); + color: var(--h2-markdown); + border-bottom: 1px solid var(--hx-markdown-underline); + padding-bottom: 5px; +} + +.markdown > h3 { + --ifm-h3-font-size: 1.5rem; + margin-bottom: 0.8rem; + margin-top: calc(var(--ifm-h3-vertical-rhythm-top) * 0rem); + color: var(--h3-markdown); + border-bottom: 1px solid var(--hx-markdown-underline); + padding-bottom: 5px; +} + +.markdown > h4 { + color: var(--h4-markdown); + border-bottom: 1px solid var(--hx-markdown-underline); + padding-bottom: 5px; +} + +.markdown > h5 { + color: var(--h5-markdown); +} + +.markdown > h6 { + color: var(--h6-markdown); +} + +.navbar { + background-color: var(--sidebar-bg-color); + box-shadow: var(--ifm-navbar-shadow); + padding: 24px 48px; + height: auto; +} + +.navbar__title { + font-size: 1.155rem; +} + +.navbar__item { + font-size: 1rem; +} + +.navbar__link:hover, +.navbar__link--active { + color: var(--primary-blue-600); + text-decoration: none; +} + +.navbar__items--right > .navbar__item:not(:first-of-type) { + margin-left: 0.25px; +} + +.dropdown__link:hover { + color: #2563eb; +} + +.dropdown__link--active { + background-color: transparent; +} + +.dropdown__link { + color: var(--ifm-navbar-link-color); +} + +.header-github-link:hover { + opacity: 0.7; +} + +.header-youtube-link:hover { + opacity: 0.7; +} + +.youtube-button { + background: linear-gradient(90deg, #ff3600 0%, #ff8100 100%); + border: none; + border-radius: 4px; + padding: 7px 21px; + color: #fff; + font-weight: bold; + font-size: 14px; + text-decoration: none; + display: inline-flex; + margin-right: 2.75rem; +} + +.github-button { + background: linear-gradient(90deg, #ff3600 0%, #ff8100 100%); + border: none; + border-radius: 4px; + padding: 7px 21px; + color: #fff; + font-weight: bold; + font-size: 14px; + text-decoration: none; + display: inline-flex; + margin-right: 2.75rem; +} + +.github-button:hover { + color: #fff; + text-decoration: none; +} + +.youtube-button:hover { + color: #fff; + text-decoration: none; +} + +.header-github-link:before { + content: ''; + width: 20px; + height: 20px; + display: flex; + background: url('/img/icons/github-dark.svg') no-repeat; + position: relative; + right: 8px; + top: 1.5px; +} + +.header-twitter-link:before { + content: ''; + width: 15px; + height: 15px; + display: flex; + background: url('/img/icons/twitter.svg') no-repeat 90% 100%; + position: relative; + right: 6px; + top: 2px; +} + +.header-youtube-link:before { + content: ''; + width: 25px; + height: 30px; + display: flex; + background: url('/img/icons/youtube.svg') no-repeat; + position: relative; + right: 8px; + top: 4.5px; +} + +.footer--dark { + --ifm-footer-background-color: #111927; +} + +.footer--dark li { + margin-bottom: 0; + line-height: normal; +} + +.footer__icon { + margin: 0; + padding: 2px; + color: #fff; + font-size: 1rem; +} + +.footer__icon:before { + content: ''; + display: inline-flex; + height: 16px; + width: 16px; + background-color: #fff; +} + +.footer__icon:hover:before { + background-color: var(--ifm-navbar-link-hover-color); +} + +.footer__github:before { + mask: url(/img/icons/github.svg) no-repeat 100% 100%; + mask-size: cover; +} + +.footer__slack:before { + mask: url(/img/icons/slack.svg) no-repeat 100% 100%; + mask-size: cover; +} + +.footer__facebook:before { + mask: url(/img/icons/facebook.svg) no-repeat 100% 100%; + mask-size: cover; +} + +.footer__instagram:before { + mask: url(/img/icons/instagram.svg) no-repeat 100% 100%; + mask-size: cover; +} + +.footer__twitter:before { + mask: url(/img/icons/twitter.svg) no-repeat 100% 100%; + mask-size: cover; +} + +.footer__news:before { + mask: url(/img/icons/source.svg) no-repeat 100% 100%; + mask-size: cover; +} + +.footer__contact:before { + mask: url(/img/icons/source.svg) no-repeat 100% 100%; + mask-size: cover; +} + +.footer__opportunities:before { + mask: url(/img/icons/opportunities.svg) no-repeat 100% 100%; + mask-size: cover; +} + +.footer__team:before { + mask: url(/img/icons/team.svg) no-repeat 100% 100%; + mask-size: cover; +} + +html[data-theme='dark'] .header-github-link:before { + background: url(/img/icons/github.svg) no-repeat; +} +html[data-theme='dark'] .header-youtube-link:before { + background: url(/img/icons/youtube-white.svg) no-repeat; +} + +@media (max-width: 996px) { + .navbar__item.github-button { + display: none; + } + .github-button { + margin: var(--ifm-menu-link-padding-vertical) + var(--ifm-menu-link-padding-horizontal); + } + .navbar__item.youtube-button { + display: none; + } + .youtube-button { + margin: var(--ifm-menu-link-padding-vertical) + var(--ifm-menu-link-padding-horizontal); + } + .center { + text-align: center; + } +} + +@media (max-width: 1000px) { + .navbar__items--right > .navbar__item:not(:first-of-type) { + margin-left: 0.25rem; + } + .github-button { + margin-right: 0.5rem; + } + .youtube-button { + margin-right: 0.5rem; + } + .hero__title { + font-size: 2rem; + } +} + +@media (max-width: 1149px) and (min-width: 1050px) { + .navbar__items--right > .navbar__item:not(:first-of-type) { + margin-left: 1.5rem; + } + .github-button { + margin-right: 0.5rem; + } + .youtube-button { + margin-right: 0.5rem; + } +} + +@media (max-width: 1049px) and (min-width: 1001px) { + .navbar__items--right > .navbar__item:not(:first-of-type) { + margin-left: 0.5rem; + } + .github-button { + margin-right: 0.5rem; + } + .youtube-button { + margin-right: 0.5rem; + } +} + +h1 { + font-size: 1.75rem; + font-weight: 700; +} +.Heading { + font-size: 1.5rem; + font-weight: 700; + text-align: center !important; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + color: var(--secondary-blue-900); + margin: 20px 0 !important; +} + +p, +textarea, +li, +.Heading div { + margin-bottom: 1.25rem; + color: var(--primary-neutral-800); + font-size: 0.9375rem; + line-height: 1.625rem; +} + +a { + color: #2563eb; + text-decoration: none; +} + +a:hover { + color: #86a7ef; +} + +/* Hide external link svg on Navbar */ +.iconExternalLink_nPIU { + display: none !important; } diff --git a/docs/src/css/index.css b/docs/src/css/index.css new file mode 100644 index 000000000..e58696a6a --- /dev/null +++ b/docs/src/css/index.css @@ -0,0 +1,593 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +:root { + /* Brand colors */ + --brand: #febc59; + + /* Dark theme */ + --dark: #282c34; + --deepdark: #20232a; + + /* Text and subtle colors */ + --text: #1a1a1a; + --subtle: #767676; + --divider: #ececec; + + /* UI and components */ + --tintColor: #f7f7f7; + --rn-note-background: rgba(255, 229, 100, 0.25); + --ifm-font-family-base: 'Optimistic Display', system-ui, -apple-system, + sans-serif; + --ifm-font-size-base: 17px; + --ifm-spacing-horizontal: 16px; + --ifm-menu-link-padding-horizontal: 0; + --ifm-toc-padding-vertical: 6px; + --ifm-toc-link-color: var(--ifm-color-emphasis-700); + --ifm-code-font-size: 90%; + --ifm-code-border-radius: 3px; + --ifm-blockquote-color: var(--ifm-font-color-base); + --ifm-blockquote-font-size: 16px; + --ifm-table-head-color: var(--subtle); + --ifm-link-hover-decoration: none; +} + +.navbar__brand { + color: var(--brand-color); + font-size: 20px; +} + +/* Custom button */ +.custom-button { + margin-right: 10px; +} + +/* Homepage */ +.homepage { + width: 100%; + max-width: 100%; +} + +/* Header Hero */ + +.HeaderHero { + padding-top: 20px; + margin-top: 4rem; + margin-bottom: 0; +} + +.HeaderHero .TwoColumns .column { + max-width: initial; +} + +.HeaderHero .TwoColumns .column.right { + text-align: end; +} + +.HeaderHero .socialLinks { + display: flex; + justify-content: flex-end; + max-width: 1200px; + margin: -10px auto 0; +} + +.HeaderHero .socialLinks .twitter-follow-button, +.HeaderHero .socialLinks .github-button .slack-button { + margin-right: 1rem; +} + +.HeaderHero .TwoColumns { + align-items: center; +} + +.HeaderHero .title { + font-size: 3rem; + line-height: 1; + margin-top: 0; + margin-bottom: 20px; + font-weight: 500; + left: -250px; + opacity: 1.3; +} + +.HeaderHero .tagline { + font-size: 1.5rem; + line-height: 1.3; + font-weight: 500; + margin-top: -7px; + opacity: 1.1; + left: -250px; +} + +.HeaderHero .description { + font-size: 1.2rem; + line-height: 1.3; + color: var(--primary-neutral-800); + opacity: 1.1; + left: -250px; +} + +.HeaderHero .buttons { + margin-top: 40px; +} + +.HeaderHero .image { + display: flex; + align-items: center; + justify-content: center; +} + +@media only screen and (min-width: 961px) { + .HeaderHero .TwoColumns { + grid-template-columns: 3fr 2fr; + } + + .HeaderHero .TwoColumns .column.left { + padding-right: 0; + width: fit-content; + } +} + +@media only screen and (max-width: 760px) { + .HeaderHero .title { + font-size: 60px; + } + + .HeaderHero .tagline { + font-size: 30px; + } + + .HeaderHero .socialLinks { + margin-top: -2rem; + } + .HeaderHero { + margin-top: 0.5rem; + } +} + +@keyframes bounce { + 0%, + 20%, + 50%, + 80%, + 100% { + transform: translateY(0); + } + + 40% { + transform: translateY(-30px); + } + + 60% { + transform: translateY(-15px); + } +} + +.bounce-animation { + animation: bounce 2s; +} + +/* Second Panel */ + +.SecondPanel { + overflow: hidden; +} + +@media only screen and (max-width: 960px) { + .SecondPanel .column.last { + max-height: 300px; + } +} + +@media only screen and (min-width: 481px) and (max-width: 960px) { + .SecondPanel .column.last { + width: 66.7%; + margin: 0 auto; + } +} + +@media only screen and (min-width: 961px) { + .SecondPanel { + max-height: 600px; + } + + /* Correct for whitespace in the image of phones */ + .SecondPanel .column.left { + margin-top: -25px; + } +} + +.second-panel-image { + width: 140%; + height: auto; + position: relative; + top: -50px; + left: -65px; + opacity: 2; +} + +/* Third Panel */ + +.third-panel-image { + width: 80%; + height: auto; + position: relative; + top: 30px; + right: -130px; + opacity: 2; +} + +/* Fourth Panel */ + +.NativeDevelopment { + overflow-y: hidden; +} +.NativeDevelopment .left { + margin: auto; +} + +.NativeDevelopment .dissection { + position: relative; +} + +.NativeDevelopment .dissection img { + left: 0; + top: 0; +} + +@media only screen and (max-width: 960px) { + .NativeDevelopment .TwoColumns { + grid-gap: 2rem; + } + .NativeDevelopment .dissection { + display: none; + } +} + +@media only screen and (max-width: 480px) { + .NativeDevelopment .dissection { + height: 350px; + } +} + +@media only screen and (min-width: 481px) and (max-width: 960px) { + .NativeDevelopment .dissection { + height: 450px; + } +} + +@media only screen and (min-width: 961px) { + .NativeDevelopment .dissection { + height: auto; + } +} + +.fourth-panel-image { + width: 75%; + height: auto; + position: relative; + top: -50px; + left: -120px; + opacity: 2; +} + +/* Fifth Panel */ + +.fifth-panel { + min-height: 550px; +} + +.fifth-panel-image { + width: 75%; + height: auto; + position: relative; + opacity: 2; + right: -70px; + margin-top: 20px; +} + +.panel-height { + height: 50px; +} + +.text-column-offset { + padding-top: 95px; +} + +/* Correction for the bottom space in section six*/ +#docusaurus_skipToContent_fallback + > section.Section.SixthPanel.tint + > div + > div.column.last.right + > div { + margin-bottom: 0; +} + +.SixthPanel { + min-height: 430px; + display: flex; + flex-direction: column; + justify-content: center; +} + +.SixthPanel .column.last { + margin-bottom: -49px; +} + +.SixthPanel pre { + margin: 0; +} + +.SixthPanel .prism-code { + border-radius: 0; + font-size: 80%; + height: 450px; + width: 100%; +} + +@media only screen and (max-width: 480px) { + .SixthPanel .column.last { + width: 100%; + padding: 0; + overflow-x: auto; + } + + .SixthPanel .prism-code { + font-size: 10px; + padding: 1.25rem 1.25rem; + } +} + +@media screen and (min-width: 481px) and (max-width: 960px) { + .SixthPanel .TwoColumns { + grid-gap: 2rem; + } + + .SixthPanel .column.last { + width: 100%; + padding: 0; + height: 28rem; + overflow-y: scroll; + } + + .SixthPanel .prism-code { + border-radius: 0; + font-size: 80%; + background-color: #fff; + color: #f8f8f2; + } +} + +@media only screen and (min-width: 961px) { + .SixthPanel .TwoColumns .column.right { + max-width: 100%; + padding-left: 0; + } + + .SixthPanel .TwoColumns { + justify-content: space-between; + } + + .SixthPanel .column.right .prism-code { + /* Bleed background into the right */ + margin-right: -9999px; + padding: 16px 1.5rem; + height: 460px; + } + .SixthPanel .column.left h2 { + text-align: left; + } + .SixthPanel .column.right .prism-code { + background-color: var(--ifm-color-emphasis-100) !important; + box-shadow: var(--ifm-navbar-shadow); + } +} + +/* Seventh Panel */ + +.SeventhPanel { + min-height: 550px; +} + +.seventh-panel-image { + width: 500%; + height: auto; + position: relative; + opacity: 2; +} + +.seventh-panel .content { + max-width: 900px; + margin: 0 auto; + display: flex; + flex-direction: column; +} + +@media only screen and (max-width: 480px) { + .seventh-panel .Heading { + width: 100%; + padding: 0 1rem; + margin-bottom: 1.5rem; + } +} + +@media only screen and (min-width: 481px) and (max-width: 960px) { + .seventh-panel .Heading { + width: 100%; + padding: 0 4rem; + margin-bottom: 1.5rem; + } + + .seventh-panel .AppList { + width: 100%; + max-width: 500px; + margin: 2rem auto; + } +} + +@media only screen and (min-width: 961px) { + .seventh-panel .column.first { + border-right: 1px solid var(--ifm-table-border-color); + } +} + +.eigth-panel-image { + width: 1000%; + height: auto; + position: relative; + opacity: 2; +} + +/* ActionButton */ + +.ActionButton { + padding: 0.75rem 1.25rem; + text-align: center; + font-size: 1.2rem; + font-weight: var(--ifm-button-font-weight); + text-decoration: none !important; + border-bottom: none; + transition: all 0.2s ease-out; + max-width: 50%; + border-radius: 0.375rem; + margin-right: 10px; +} + +.ActionButton.primary { + color: var(--base-neutral-0); + background-color: var(--ifm-button-background-color); + border: var(--ifm-button-border-width) solid var(--ifm-button-border-color); + text-wrap: nowrap; +} + +.ActionButton.primary:hover { + background-color: #1cbb99; +} + +.ActionButton.secondary { + color: #1c1e21; + background-color: #ebedf0; +} + +.ActionButton.secondary:hover { + background-color: #c7c7c7; +} + +.ActionButton.secondary::after { + content: '›'; + font-size: 24px; + margin-left: 5px; +} + +@media only screen and (max-width: 480px) { + .ActionButton { + max-width: 100%; + width: 100%; + display: block; + white-space: nowrap; + } + + .ActionButton.secondary { + margin-top: 1rem; + margin-left: auto; + } + + .custom-image { + width: 80%; + padding-top: 60px; + } +} + +.HomePage { + width: 100%; + overflow-x: hidden; +} + +/* Section */ + +.Section { + width: 100%; + padding-top: 50px; + padding-bottom: 50px; + overflow-x: hidden; + margin-bottom: 5rem; +} + +@media only screen and (max-width: 960px) { + .Section { + margin-bottom: 2rem; + padding-top: 1rem; + } +} + +.Section.tint { + background-color: var(--ifm-hover-overlay); +} + +.Section.dark { + background-color: var(--dark); +} + +/* Two columns */ + +.TwoColumns { + display: grid; +} + +.TwoColumns .column { + width: 100%; +} + +.TwoColumns .column.first { + grid-area: first; +} + +.TwoColumns .column.last { + grid-area: last; +} + +@media only screen and (min-width: 961px) { + .TwoColumns { + margin: 0 auto; + grid-template-columns: repeat(2, 1fr); + grid-template-areas: 'first last'; + } + + .TwoColumns.reverse { + grid-template-areas: 'last first'; + } + + .TwoColumns .column { + max-width: 450px; + } + + .TwoColumns .column.left { + padding-right: 50px; + } + + .TwoColumns .column.right { + padding-left: 50px; + } +} + +@media only screen and (max-width: 960px) { + .TwoColumns, + .TwoColumns.reverse { + grid-template-columns: 1fr; + grid-template-areas: 'first' 'last'; + } + + .TwoColumns .column { + padding: 0 4rem; + } +} + +@media only screen and (max-width: 480px) { + .TwoColumns .column { + padding: 0 1.25rem; + } +} diff --git a/docs/static/img/icons/facebook.svg b/docs/static/img/icons/facebook.svg new file mode 100644 index 000000000..e8d1443db --- /dev/null +++ b/docs/static/img/icons/facebook.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/static/img/icons/favicon_palisadoes.ico b/docs/static/img/icons/favicon_palisadoes.ico new file mode 100644 index 000000000..0675af293 Binary files /dev/null and b/docs/static/img/icons/favicon_palisadoes.ico differ diff --git a/docs/static/img/icons/github-dark.svg b/docs/static/img/icons/github-dark.svg new file mode 100644 index 000000000..654102ae5 --- /dev/null +++ b/docs/static/img/icons/github-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/static/img/icons/github.svg b/docs/static/img/icons/github.svg new file mode 100644 index 000000000..ab49f9955 --- /dev/null +++ b/docs/static/img/icons/github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/static/img/icons/instagram.svg b/docs/static/img/icons/instagram.svg new file mode 100644 index 000000000..0b5c5cef0 --- /dev/null +++ b/docs/static/img/icons/instagram.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/static/img/icons/opportunities.svg b/docs/static/img/icons/opportunities.svg new file mode 100644 index 000000000..85a807cee --- /dev/null +++ b/docs/static/img/icons/opportunities.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/docs/static/img/icons/slack.svg b/docs/static/img/icons/slack.svg new file mode 100644 index 000000000..f4aa6e6d6 --- /dev/null +++ b/docs/static/img/icons/slack.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/static/img/icons/source.svg b/docs/static/img/icons/source.svg new file mode 100644 index 000000000..1d93acb91 --- /dev/null +++ b/docs/static/img/icons/source.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/static/img/icons/team.svg b/docs/static/img/icons/team.svg new file mode 100644 index 000000000..29dfc5b0e --- /dev/null +++ b/docs/static/img/icons/team.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/static/img/icons/twitter.svg b/docs/static/img/icons/twitter.svg new file mode 100644 index 000000000..18eb8d341 --- /dev/null +++ b/docs/static/img/icons/twitter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/static/img/icons/youtube-white.svg b/docs/static/img/icons/youtube-white.svg new file mode 100644 index 000000000..2cf08c84d --- /dev/null +++ b/docs/static/img/icons/youtube-white.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/static/img/icons/youtube.svg b/docs/static/img/icons/youtube.svg new file mode 100644 index 000000000..97a61232b --- /dev/null +++ b/docs/static/img/icons/youtube.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/static/img/markdown/offline/High_level_offline_action_flow.png b/docs/static/img/markdown/offline/High_level_offline_action_flow.png new file mode 100644 index 000000000..73c8888d6 Binary files /dev/null and b/docs/static/img/markdown/offline/High_level_offline_action_flow.png differ diff --git a/docs/static/img/markdown/offline/perform_action_wrapper_flow.png b/docs/static/img/markdown/offline/perform_action_wrapper_flow.png new file mode 100644 index 000000000..974771138 Binary files /dev/null and b/docs/static/img/markdown/offline/perform_action_wrapper_flow.png differ diff --git a/docs/static/img/markdown/talawa_lint/lint_warning_ex.png b/docs/static/img/markdown/talawa_lint/lint_warning_ex.png new file mode 100644 index 000000000..ac32666ac Binary files /dev/null and b/docs/static/img/markdown/talawa_lint/lint_warning_ex.png differ diff --git a/docs/static/img/markdown/talawa_lint/no_lint_ex.png b/docs/static/img/markdown/talawa_lint/no_lint_ex.png new file mode 100644 index 000000000..3bf8146fd Binary files /dev/null and b/docs/static/img/markdown/talawa_lint/no_lint_ex.png differ