Gate Flutter app behind Telegram
Some checks failed
Build and deploy Flutter Web / build (push) Failing after 3m19s

This commit is contained in:
Ruslan Bakiev
2026-05-08 17:41:53 +07:00
parent 383e4d2307
commit 5b7b5771a1
7 changed files with 110 additions and 13 deletions

View File

@@ -36,6 +36,7 @@ jobs:
--tag "$IMAGE" \
--build-arg MAPBOX_ACCESS_TOKEN="${{ secrets.MAPBOX_ACCESS_TOKEN }}" \
--build-arg MAPBOX_STYLE="mapbox/streets-v12" \
--build-arg TELEGRAM_BOT_URL="https://t.me/carfteebot" \
.
- name: Skip stale deployment

View File

@@ -2,12 +2,14 @@ FROM ghcr.io/cirruslabs/flutter:stable AS build
WORKDIR /app
ARG MAPBOX_ACCESS_TOKEN=""
ARG MAPBOX_STYLE="mapbox/streets-v12"
ARG TELEGRAM_BOT_URL="https://t.me/carfteebot"
COPY pubspec.* ./
RUN flutter pub get
COPY . .
RUN flutter build web --release \
--dart-define=MAPBOX_ACCESS_TOKEN="$MAPBOX_ACCESS_TOKEN" \
--dart-define=MAPBOX_STYLE="$MAPBOX_STYLE"
--dart-define=MAPBOX_STYLE="$MAPBOX_STYLE" \
--dart-define=TELEGRAM_BOT_URL="$TELEGRAM_BOT_URL"
FROM nginx:1.27-alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf

View File

@@ -0,0 +1,2 @@
export 'telegram_launcher_stub.dart'
if (dart.library.js_interop) 'telegram_launcher_web.dart';

View File

@@ -0,0 +1 @@
void openTelegramUrl(String url) {}

View File

@@ -0,0 +1,8 @@
import 'dart:js_interop';
@JS('window.open')
external JSAny? _open(JSString url, JSString target, JSString features);
void openTelegramUrl(String url) {
_open(url.toJS, '_blank'.toJS, 'noopener,noreferrer'.toJS);
}

View File

@@ -5,6 +5,7 @@ import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:latlong2/latlong.dart';
import '../auth/telegram_launcher.dart';
import '../models/place_models.dart';
import '../state/place_controller.dart';
@@ -13,6 +14,10 @@ const _mapboxStyle = String.fromEnvironment(
'MAPBOX_STYLE',
defaultValue: 'mapbox/streets-v12',
);
const _telegramBotUrl = String.fromEnvironment(
'TELEGRAM_BOT_URL',
defaultValue: 'https://t.me/carfteebot',
);
class MapflowShell extends ConsumerWidget {
const MapflowShell({super.key});
@@ -22,7 +27,12 @@ class MapflowShell extends ConsumerWidget {
final asyncState = ref.watch(placeControllerProvider);
return asyncState.when(
data: (state) => _MapContent(state: state),
data: (state) => state.hasTelegramAuth
? _MapContent(state: state)
: _TelegramAuthGate(
onOpenTelegram: () => openTelegramUrl(_telegramBotUrl),
onRetry: () => ref.invalidate(placeControllerProvider),
),
loading: () => const _MapLoading(),
error: (error, _) => _MapError(message: error.toString()),
);
@@ -119,6 +129,71 @@ class _MapContent extends ConsumerWidget {
}
}
class _TelegramAuthGate extends StatelessWidget {
const _TelegramAuthGate({
required this.onOpenTelegram,
required this.onRetry,
});
final VoidCallback onOpenTelegram;
final VoidCallback onRetry;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
FlutterMap(
options: const MapOptions(
initialCenter: LatLng(10.7718, 106.6982),
initialZoom: 14.2,
),
children: [const _BaseMapTileLayer(), const _MapAttribution()],
),
ColoredBox(color: Colors.black.withValues(alpha: 0.18)),
SafeArea(
child: Align(
alignment: Alignment.bottomCenter,
child: Container(
width: double.infinity,
margin: const EdgeInsets.all(12),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFFFFFBF5),
borderRadius: BorderRadius.circular(8),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'Открой через Telegram, чтобы продолжить.',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w900,
letterSpacing: 0,
),
),
const SizedBox(height: 12),
FilledButton.icon(
onPressed: onOpenTelegram,
icon: const Icon(Icons.telegram),
label: const Text('Telegram'),
),
TextButton(
onPressed: onRetry,
child: const Text('Обновить'),
),
],
),
),
),
),
],
),
);
}
}
class _MapLoading extends StatelessWidget {
const _MapLoading();
@@ -132,10 +207,7 @@ class _MapLoading extends StatelessWidget {
initialCenter: LatLng(10.7718, 106.6982),
initialZoom: 14.2,
),
children: [
const _BaseMapTileLayer(),
const _MapAttribution(),
],
children: [const _BaseMapTileLayer(), const _MapAttribution()],
),
const Center(child: CircularProgressIndicator()),
],
@@ -159,10 +231,7 @@ class _MapError extends StatelessWidget {
initialCenter: LatLng(10.7718, 106.6982),
initialZoom: 14.2,
),
children: [
const _BaseMapTileLayer(),
const _MapAttribution(),
],
children: [const _BaseMapTileLayer(), const _MapAttribution()],
),
SafeArea(
child: Align(
@@ -218,9 +287,7 @@ class _MapAttribution extends StatelessWidget {
Widget build(BuildContext context) {
if (_mapboxAccessToken.isEmpty) {
return const RichAttributionWidget(
attributions: [
TextSourceAttribution('OpenStreetMap contributors'),
],
attributions: [TextSourceAttribution('OpenStreetMap contributors')],
);
}

View File

@@ -67,6 +67,22 @@ class PlaceController extends AsyncNotifier<PlaceState> {
@override
Future<PlaceState> build() async {
if (!_api.hasTelegramAuth) {
return const PlaceState(
selectedTrait: PlaceTrait.calm,
places: [],
selectedPlaceId: null,
currentUser: null,
hasTelegramAuth: false,
reviewDraft: VoiceReviewDraft(
placeName: '',
duration: Duration.zero,
extractedTraits: {},
evidence: [],
),
);
}
final currentUser = await _api.authenticateTelegram();
final places = await _api.fetchPlaces();
return PlaceState(