Gate Flutter app behind Telegram
Some checks failed
Build and deploy Flutter Web / build (push) Failing after 3m19s
Some checks failed
Build and deploy Flutter Web / build (push) Failing after 3m19s
This commit is contained in:
@@ -36,6 +36,7 @@ jobs:
|
|||||||
--tag "$IMAGE" \
|
--tag "$IMAGE" \
|
||||||
--build-arg MAPBOX_ACCESS_TOKEN="${{ secrets.MAPBOX_ACCESS_TOKEN }}" \
|
--build-arg MAPBOX_ACCESS_TOKEN="${{ secrets.MAPBOX_ACCESS_TOKEN }}" \
|
||||||
--build-arg MAPBOX_STYLE="mapbox/streets-v12" \
|
--build-arg MAPBOX_STYLE="mapbox/streets-v12" \
|
||||||
|
--build-arg TELEGRAM_BOT_URL="https://t.me/carfteebot" \
|
||||||
.
|
.
|
||||||
|
|
||||||
- name: Skip stale deployment
|
- name: Skip stale deployment
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ FROM ghcr.io/cirruslabs/flutter:stable AS build
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ARG MAPBOX_ACCESS_TOKEN=""
|
ARG MAPBOX_ACCESS_TOKEN=""
|
||||||
ARG MAPBOX_STYLE="mapbox/streets-v12"
|
ARG MAPBOX_STYLE="mapbox/streets-v12"
|
||||||
|
ARG TELEGRAM_BOT_URL="https://t.me/carfteebot"
|
||||||
COPY pubspec.* ./
|
COPY pubspec.* ./
|
||||||
RUN flutter pub get
|
RUN flutter pub get
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN flutter build web --release \
|
RUN flutter build web --release \
|
||||||
--dart-define=MAPBOX_ACCESS_TOKEN="$MAPBOX_ACCESS_TOKEN" \
|
--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
|
FROM nginx:1.27-alpine
|
||||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|||||||
2
lib/auth/telegram_launcher.dart
Normal file
2
lib/auth/telegram_launcher.dart
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export 'telegram_launcher_stub.dart'
|
||||||
|
if (dart.library.js_interop) 'telegram_launcher_web.dart';
|
||||||
1
lib/auth/telegram_launcher_stub.dart
Normal file
1
lib/auth/telegram_launcher_stub.dart
Normal file
@@ -0,0 +1 @@
|
|||||||
|
void openTelegramUrl(String url) {}
|
||||||
8
lib/auth/telegram_launcher_web.dart
Normal file
8
lib/auth/telegram_launcher_web.dart
Normal 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);
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import 'package:flutter_map/flutter_map.dart';
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
|
|
||||||
|
import '../auth/telegram_launcher.dart';
|
||||||
import '../models/place_models.dart';
|
import '../models/place_models.dart';
|
||||||
import '../state/place_controller.dart';
|
import '../state/place_controller.dart';
|
||||||
|
|
||||||
@@ -13,6 +14,10 @@ const _mapboxStyle = String.fromEnvironment(
|
|||||||
'MAPBOX_STYLE',
|
'MAPBOX_STYLE',
|
||||||
defaultValue: 'mapbox/streets-v12',
|
defaultValue: 'mapbox/streets-v12',
|
||||||
);
|
);
|
||||||
|
const _telegramBotUrl = String.fromEnvironment(
|
||||||
|
'TELEGRAM_BOT_URL',
|
||||||
|
defaultValue: 'https://t.me/carfteebot',
|
||||||
|
);
|
||||||
|
|
||||||
class MapflowShell extends ConsumerWidget {
|
class MapflowShell extends ConsumerWidget {
|
||||||
const MapflowShell({super.key});
|
const MapflowShell({super.key});
|
||||||
@@ -22,7 +27,12 @@ class MapflowShell extends ConsumerWidget {
|
|||||||
final asyncState = ref.watch(placeControllerProvider);
|
final asyncState = ref.watch(placeControllerProvider);
|
||||||
|
|
||||||
return asyncState.when(
|
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(),
|
loading: () => const _MapLoading(),
|
||||||
error: (error, _) => _MapError(message: error.toString()),
|
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 {
|
class _MapLoading extends StatelessWidget {
|
||||||
const _MapLoading();
|
const _MapLoading();
|
||||||
|
|
||||||
@@ -132,10 +207,7 @@ class _MapLoading extends StatelessWidget {
|
|||||||
initialCenter: LatLng(10.7718, 106.6982),
|
initialCenter: LatLng(10.7718, 106.6982),
|
||||||
initialZoom: 14.2,
|
initialZoom: 14.2,
|
||||||
),
|
),
|
||||||
children: [
|
children: [const _BaseMapTileLayer(), const _MapAttribution()],
|
||||||
const _BaseMapTileLayer(),
|
|
||||||
const _MapAttribution(),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
const Center(child: CircularProgressIndicator()),
|
const Center(child: CircularProgressIndicator()),
|
||||||
],
|
],
|
||||||
@@ -159,10 +231,7 @@ class _MapError extends StatelessWidget {
|
|||||||
initialCenter: LatLng(10.7718, 106.6982),
|
initialCenter: LatLng(10.7718, 106.6982),
|
||||||
initialZoom: 14.2,
|
initialZoom: 14.2,
|
||||||
),
|
),
|
||||||
children: [
|
children: [const _BaseMapTileLayer(), const _MapAttribution()],
|
||||||
const _BaseMapTileLayer(),
|
|
||||||
const _MapAttribution(),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
SafeArea(
|
SafeArea(
|
||||||
child: Align(
|
child: Align(
|
||||||
@@ -218,9 +287,7 @@ class _MapAttribution extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (_mapboxAccessToken.isEmpty) {
|
if (_mapboxAccessToken.isEmpty) {
|
||||||
return const RichAttributionWidget(
|
return const RichAttributionWidget(
|
||||||
attributions: [
|
attributions: [TextSourceAttribution('OpenStreetMap contributors')],
|
||||||
TextSourceAttribution('OpenStreetMap contributors'),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,22 @@ class PlaceController extends AsyncNotifier<PlaceState> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<PlaceState> build() async {
|
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 currentUser = await _api.authenticateTelegram();
|
||||||
final places = await _api.fetchPlaces();
|
final places = await _api.fetchPlaces();
|
||||||
return PlaceState(
|
return PlaceState(
|
||||||
|
|||||||
Reference in New Issue
Block a user