Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .pubignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
assets/*.png
assets/css/
build/
.dart_tool/
.idea/
.github/
SINT_GETX_COMPARISON.docx
SINT_GETX_COMPARISON.md
SINT_GETX_EVOLUTION_BRIEF.md
_config.yml
index.md
41 changes: 41 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,44 @@
## [1.2.1] - 2026-02-28

- **README images**: Switched to absolute GitHub raw URLs so images render correctly on pub.dev (`.pubignore` excludes PNGs from the package).

---

## [1.2.0] - 2026-02-28

RESTful Navigation & i18n URL Routing.

133 lines of new code. Zero new dependencies. Pillars N and T upgraded.

### Pillar N (Navigation)

- **RESTful Route Parameters** (Spring Boot-inspired API):
- `Sint.routeParam` — Primary path parameter value. For route `/book/:bookId` navigated as `/book/abc123`, returns `'abc123'`. Equivalent to Spring Boot's `@PathVariable`.
- `Sint.pathParam('bookId')` — Named path parameter. Equivalent to `@PathVariable("bookId")`.
- `Sint.queryParam('page')` — Query parameter from URL. Equivalent to `@RequestParam`.
- `Sint.queryParamOrDefault('sort', 'recent')` — Query parameter with fallback. Equivalent to `@RequestParam(defaultValue = "recent")`.
- Full test mode support via `SintTestMode`.
- **`translateEndpoints` flag**: New parameter on `SintMaterialApp` and `ConfigData` that enables automatic i18n URL routing. When `true`, SINT builds a `PathTranslator` from registered translations and routes.
- **`setUrlStrategy()` resilience**: Wrapped in try-catch to handle "URL strategy already set" when the Flutter engine is already initialized — prevents web startup crashes on hot restart.

### Pillar T (Translation)

- **`PathTranslator`** — New class for internationalized URL routing:
- `canonicalizePath()` — Converts localized URLs to canonical English before route matching. e.g. `/libro/abc123` → `/book/abc123`.
- `localizePath()` — Converts canonical URLs to the current locale for the browser URL bar. e.g. `/book/abc123` → `/libro/abc123` (ES) or `/livre/abc123` (FR).
- `extractSegments()` — Automatically extracts static route segments from registered `SintPage` names (skips `:param` segments).
- Built-in diacritics normalization (`Publicación` → `publicacion`) for clean URLs.
- Zero-config: built automatically from existing app translations when `translateEndpoints: true`. No external localization file needed.
- **`Sint.pathTranslator`** — Getter/setter on the `SintInterface` to access the URL translator. Stored in `IntlHost` and cleaned up on `SintRoot.onClose()`.
- **`SintInformationParser` integration** — Automatic canonicalization on `parseRouteInformation()` and localization on `restoreRouteInformation()`. Browser URL bar shows localized paths; internal routing uses canonical English.

### Housekeeping

- **Example app**: Added `example/main.dart` demonstrating all four SINT pillars (State, Injection, Navigation, Translation) in a counter app. Targets 160/160 pub points.
- **TickerMode.of deprecation**: Suppressed for cross-SDK compatibility in `RxTickerProviderMixin`.

---

## [1.1.0] - 2026-02-26

The Four Pillars Evolve — Workers, Pattern Matching, Async DI & Web-Safe Navigation.
Expand Down
203 changes: 196 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# SINT

<p align="center">
<img src="https://raw.githubusercontent.com/Open-Neom/sint/main/assets/SINT%20-%20Logo%20-%202026.png" alt="SINT Framework" width="280"/>
</p>

**State, Injection, Navigation, Translation — The Four Pillars of High-Fidelity Flutter Infrastructure.**

[![pub package](https://img.shields.io/pub/v/sint.svg?label=sint&color=blue)](https://pub.dev/packages/sint)
Expand All @@ -26,13 +30,15 @@
---

- [About SINT](#about-sint)
- [What's New in 1.2.0](#whats-new-in-120)
- [What's New in 1.1.0](#whats-new-in-110)
- [Installing](#installing)
- [The Four Pillars](#the-four-pillars)
- [State Management (S)](#state-management-s)
- [Injection (I)](#injection-i)
- [Navigation (N)](#navigation-n)
- [Translation (T)](#translation-t)
- [Flutter Web & Deep Links](#flutter-web--deep-links)
- [Counter App with SINT](#counter-app-with-sint)
- [Migration from GetX](#migration-from-getx)
- [Origin & Philosophy](#origin--philosophy)
Expand All @@ -47,8 +53,8 @@ SINT is an architectural evolution of GetX (v5.0.0-rc), built as a focused frame
|---|---|
| **S** — State Management | `SintController`, `SintBuilder`, `Obx`, `.obs`, Rx types, Workers, `SintStatus`, `SintListener` |
| **I** — Injection | `Sint.put`, `Sint.find`, `Sint.lazyPut`, `Sint.putAsync`, Bindings, SmartManagement |
| **N** — Navigation | `SintPage`, `Sint.toNamed`, `Sint.toInitial`, middleware, `SintMaterialApp`, web-safe `back()` |
| **T** — Translation | `.tr` extension, `Translations` class, locale management, `loadTranslations` |
| **N** — Navigation | `SintPage`, `Sint.toNamed`, `Sint.toInitial`, `routeParam`, `pathParam`, `queryParam`, middleware, `SintMaterialApp`, `SintSnackBarStyle`, web-safe `back()` |
| **T** — Translation | `.tr` extension, `Translations` class, locale management, `loadTranslations`, `PathTranslator`, `translateEndpoints` |

Everything outside these four pillars has been removed: no HTTP client, no animations, no string validators, no generic utilities. The result is **37.7% less code** than GetX — 12,849 LOC vs 20,615 LOC.

Expand All @@ -60,6 +66,109 @@ Everything outside these four pillars has been removed: no HTTP client, no anima

---

## What's New in 1.2.0

**Focus: Flutter Web, Deep Links & i18n URL Routing — without breaking mobile.**

### RESTful Route Parameters

Spring Boot-inspired parameter extraction that works identically on mobile and web:

```dart
// Define routes with path parameters (same as before)
SintPage(name: '/book/:bookId', page: () => BookDetail()),
SintPage(name: '/shop/product/:productId', page: () => ProductPage()),

// Navigate (works on all platforms)
Sint.toNamed('/book/abc123');
Sint.toNamed('/shop/product/42?color=red&size=lg');

// Extract parameters — clean API, no manual parsing
String? bookId = Sint.routeParam; // 'abc123'
String? productId = Sint.pathParam('productId'); // '42'
String? color = Sint.queryParam('color'); // 'red'
String size = Sint.queryParamOrDefault('size', 'm'); // 'lg'
```

| Method | Equivalent (Spring Boot) | Description |
|--------|--------------------------|-------------|
| `Sint.routeParam` | `@PathVariable` | First path parameter value |
| `Sint.pathParam('id')` | `@PathVariable("id")` | Named path parameter |
| `Sint.queryParam('q')` | `@RequestParam` | Query string parameter |
| `Sint.queryParamOrDefault('sort', 'asc')` | `@RequestParam(defaultValue)` | Query with fallback |

All four methods support `SintTestMode` for unit testing without a running app.

### i18n URL Routing (translateEndpoints)

Localized URLs in the browser address bar — zero configuration beyond what you already have:

```dart
SintMaterialApp(
translateEndpoints: true, // Enable URL localization
translationsKeys: AppTranslations.keys,
locale: Locale('es'),
sintPages: [
SintPage(name: '/book/:bookId', page: () => BookDetail()),
SintPage(name: '/event/:eventId', page: () => EventDetail()),
],
)
```

Your existing translations automatically power the URL routing:

```dart
// In your translations file — no extra config needed
'es': { 'book': 'libro', 'event': 'evento', ... }
'fr': { 'book': 'livre', 'event': 'evenement', ... }
'de': { 'book': 'buch', 'event': 'veranstaltung', ... }
```

Result:

| Locale | Browser URL | Internal Route |
|--------|-------------|----------------|
| EN | `/book/abc123` | `/book/abc123` |
| ES | `/libro/abc123` | `/book/abc123` |
| FR | `/livre/abc123` | `/book/abc123` |
| DE | `/buch/abc123` | `/book/abc123` |

**How it works:**

1. `PathTranslator` is built automatically from your registered routes + translations
2. Incoming URLs are canonicalized before route matching (`/libro/x` → `/book/x`)
3. Outgoing URLs are localized for the browser bar (`/book/x` → `/libro/x`)
4. Diacritics are normalized automatically (`Publicación` → `publicacion`)
5. On mobile, `translateEndpoints` has zero overhead — path translation only activates for web URL parsing

### Global Snackbar Theming

Define snackbar appearance once, apply everywhere:

```dart
SintMaterialApp(
snackBarStyle: SintSnackBarStyle(
backgroundColor: Colors.grey[900],
colorText: Colors.white,
borderRadius: 12,
margin: EdgeInsets.all(16),
snackPosition: SnackPosition.bottom,
duration: Duration(seconds: 3),
),
)

// All snackbar calls inherit the global style
Sint.snackbar('Title', 'Message');
// Call-site params still override when needed
Sint.snackbar('Error', 'Failed', backgroundColor: Colors.red);
```

Three-level cascade: **call-site > global style > hardcoded defaults**.

See [CHANGELOG.md](CHANGELOG.md) for the full list of changes.

---

## What's New in 1.1.0

### Reactive Workers
Expand Down Expand Up @@ -146,7 +255,7 @@ Add SINT to your `pubspec.yaml`:

```yaml
dependencies:
sint: ^1.1.0
sint: ^1.2.0
```

Import it:
Expand Down Expand Up @@ -180,6 +289,10 @@ SINT is built for speed. Every pillar is audited against the Open Neom Standard.

## The Four Pillars

<p align="center">
<img src="https://raw.githubusercontent.com/Open-Neom/sint/main/assets/SINT%20-%20Framework%20-%202026.png" alt="SINT — The Four Pillars" width="700"/>
</p>

### State Management (S)

Two approaches: **Reactive** (`.obs` + `Obx`) and **Simple** (`SintBuilder`).
Expand Down Expand Up @@ -233,29 +346,41 @@ final controller = Sint.find<AuthController>();

### Navigation (N)

Route management without context:
Route management without context — optimized for web deep links and mobile alike:

```dart
SintMaterialApp(
initialRoute: '/',
translateEndpoints: true, // i18n URLs (web)
snackBarStyle: SintSnackBarStyle(...), // Global theming
sintPages: [
SintPage(name: '/', page: () => Home()),
SintPage(name: '/details', page: () => Details()),
SintPage(name: '/book/:bookId', page: () => BookDetail()),
SintPage(name: '/search', page: () => Search()),
],
)

Sint.toNamed('/details');
// Navigation
Sint.toNamed('/book/abc123?ref=home');
Sint.back(); // Web-safe
Sint.toInitial(); // Hard reset to home
Sint.toInitial(keep: {AuthController}); // Keep specific controllers

// RESTful parameter extraction
String? id = Sint.routeParam; // 'abc123'
String? id = Sint.pathParam('bookId'); // 'abc123'
String? ref = Sint.queryParam('ref'); // 'home'
String sort = Sint.queryParamOrDefault('sort', 'a'); // 'a' (default)

// Snackbar with global style
Sint.snackbar('Title', 'Message');
```

[Full documentation](documentation/en_US/navigation_management.md)

### Translation (T)

Internationalization with `.tr`:
Internationalization with `.tr` — now powers URL routing too:

```dart
Text('hello'.tr);
Expand All @@ -267,12 +392,76 @@ await Sint.loadTranslations(() async {
final json = await rootBundle.loadString('assets/i18n/shop.json');
return {'es': Map<String, String>.from(jsonDecode(json))};
});

// URL path translation (automatic when translateEndpoints: true)
// Your translation keys double as URL segment mappings:
// 'book' → 'libro' (ES), 'livre' (FR), 'buch' (DE)
//
// PathTranslator handles:
// canonicalizePath('/libro/abc') → '/book/abc' (incoming)
// localizePath('/book/abc', 'es') → '/libro/abc' (outgoing)
```

[Full documentation](documentation/en_US/translation_management.md)

---

## Flutter Web & Deep Links

SINT is designed with a **web-first, mobile-safe** philosophy. Every feature works identically across platforms, but web gets extra optimizations:

| Feature | Web Behavior | Mobile Behavior |
|---------|-------------|-----------------|
| `Sint.back()` | No-op if no internal history (browser arrows handle it) | Standard `Navigator.pop()` |
| `Sint.routeParam` | Extracted from browser URL path | Extracted from route arguments |
| `Sint.queryParam()` | Extracted from URL query string `?key=value` | Extracted from route arguments |
| `translateEndpoints` | Localizes browser URL bar + canonicalizes incoming URLs | No overhead — flag is ignored |
| `Sint.showBackButton` | `false` (browser has native arrows) | `true` |
| Default transition | `Transition.fade` (GPU-light for web canvas) | Platform default (Cupertino/Material) |
| Scroll behavior | Drag enabled for touch, mouse, and trackpad | Platform default |
| `SintSnackBarStyle` | Same styling across web and mobile | Same styling across web and mobile |

### Deep Link Example (Web + Mobile)

```dart
// 1. Define routes with parameters
SintMaterialApp(
initialRoute: '/',
translateEndpoints: true,
translationsKeys: AppTranslations.keys,
locale: Locale('es'),
sintPages: [
SintPage(name: '/', page: () => HomePage()),
SintPage(name: '/book/:bookId', page: () => BookDetail()),
SintPage(name: '/profile/:userId', page: () => ProfilePage()),
],
)

// 2. In your controller — same code works everywhere
class BookDetailController extends SintController {
late final String bookId;

@override
void onInit() {
super.onInit();
// Works from: browser URL, deep link, or Sint.toNamed()
bookId = Sint.routeParam ?? '';
loadBook(bookId);
}
}
```

**On web:** User visits `https://myapp.com/libro/abc123` →
SINT canonicalizes to `/book/abc123` → routes to `BookDetail` →
`Sint.routeParam` returns `'abc123'` → browser shows `/libro/abc123`.

**On mobile:** `Sint.toNamed('/book/abc123')` →
routes to `BookDetail` → `Sint.routeParam` returns `'abc123'`.

**Same controller. Same routes. Same parameters. Zero platform checks.**

---

## Counter App with SINT

```dart
Expand Down
Binary file added assets/SINT - Framework - 2026.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/SINT - Logo - 2026.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions lib/navigation/src/domain/extensions/navigation_extensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,37 @@ extension NavigationExtension on SintInterface {
return rootController.rootDelegate.parameters;
}

/// Primary route parameter from URL path. Returns null if none.
/// For route '/book/:bookId' navigated as '/book/abc123', returns 'abc123'.
/// Usage: `String? id = Sint.routeParam;`
String? get routeParam {
if (_shouldUseMock) return SintTestMode.routeParam;
return rootController.rootDelegate.routeParam;
}

/// Named path parameter (like Spring Boot @PathVariable).
/// Usage: `String? id = Sint.pathParam('bookId');`
String? pathParam(String name) {
if (_shouldUseMock) return SintTestMode.pathParam(name);
return rootController.rootDelegate.pathParam(name);
}

/// Query parameter from URL (like Spring Boot @RequestParam).
/// Usage: `String? page = Sint.queryParam('page');`
String? queryParam(String name) {
if (_shouldUseMock) return SintTestMode.queryParam(name);
return rootController.rootDelegate.queryParam(name);
}

/// Query parameter with default value.
/// Usage: `String sort = Sint.queryParamOrDefault('sort', 'recent');`
String queryParamOrDefault(String name, String defaultValue) {
if (_shouldUseMock) {
return SintTestMode.queryParamOrDefault(name, defaultValue);
}
return rootController.rootDelegate.queryParamOrDefault(name, defaultValue);
}

/// Casts the stored router delegate to a desired type
TDelegate? delegate<TDelegate extends RouterDelegate<TPage>, TPage>() =>
_getxController.routerDelegate as TDelegate?;
Expand Down
Loading
Loading