flutterbridge 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.js +510 -0
- package/package.json +40 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Flutter Bridge
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, cpSync, rmSync } from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
import inquirer from 'inquirer';
|
|
9
|
+
const program = new Command();
|
|
10
|
+
// Configuração da CLI
|
|
11
|
+
program
|
|
12
|
+
.name('flutterbridge')
|
|
13
|
+
.description('CLI para transformar SPAs em apps mobile usando Flutter')
|
|
14
|
+
.version('1.0.0');
|
|
15
|
+
// Comando: init
|
|
16
|
+
program
|
|
17
|
+
.command('init')
|
|
18
|
+
.description('Inicializa um novo projeto FlutterBridge')
|
|
19
|
+
.argument('[project-name]', 'Nome do projeto')
|
|
20
|
+
.action(async (projectName) => {
|
|
21
|
+
try {
|
|
22
|
+
const answers = await inquirer.prompt([
|
|
23
|
+
{
|
|
24
|
+
type: 'input',
|
|
25
|
+
name: 'name',
|
|
26
|
+
message: 'Nome do projeto:',
|
|
27
|
+
default: projectName || 'my-app',
|
|
28
|
+
validate: (input) => input.length > 0 || 'Nome é obrigatório'
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
type: 'input',
|
|
32
|
+
name: 'appId',
|
|
33
|
+
message: 'App ID (ex: com.example.app):',
|
|
34
|
+
default: 'com.example.app',
|
|
35
|
+
validate: (input) => /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/.test(input) || 'App ID inválido'
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
type: 'input',
|
|
39
|
+
name: 'appName',
|
|
40
|
+
message: 'Nome do app (exibido no dispositivo):',
|
|
41
|
+
default: (answers) => answers.name
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'list',
|
|
45
|
+
name: 'webDir',
|
|
46
|
+
message: 'Diretório de build do seu projeto web:',
|
|
47
|
+
choices: ['dist', 'build', 'public', 'www', 'outro'],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
type: 'input',
|
|
51
|
+
name: 'customWebDir',
|
|
52
|
+
message: 'Digite o diretório customizado:',
|
|
53
|
+
when: (answers) => answers.webDir === 'outro'
|
|
54
|
+
}
|
|
55
|
+
]);
|
|
56
|
+
const webDir = answers.customWebDir || answers.webDir;
|
|
57
|
+
await initProject(answers.name, answers.appId, answers.appName, webDir);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.error(chalk.red('Erro ao inicializar projeto:'), error);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
// Comando: add
|
|
64
|
+
program
|
|
65
|
+
.command('add')
|
|
66
|
+
.description('Adiciona uma plataforma (android/ios)')
|
|
67
|
+
.argument('<platform>', 'Plataforma: android ou ios')
|
|
68
|
+
.action(async (platform) => {
|
|
69
|
+
await addPlatform(platform);
|
|
70
|
+
});
|
|
71
|
+
// Comando: sync
|
|
72
|
+
program
|
|
73
|
+
.command('sync')
|
|
74
|
+
.description('Sincroniza o projeto web com o Flutter')
|
|
75
|
+
.action(async () => {
|
|
76
|
+
await syncProject();
|
|
77
|
+
});
|
|
78
|
+
// Comando: run
|
|
79
|
+
program
|
|
80
|
+
.command('run')
|
|
81
|
+
.description('Executa o app em um dispositivo/emulador')
|
|
82
|
+
.argument('<platform>', 'Plataforma: android ou ios')
|
|
83
|
+
.option('-d, --device <device>', 'ID do dispositivo específico')
|
|
84
|
+
.action(async (platform, options) => {
|
|
85
|
+
await runApp(platform, options.device);
|
|
86
|
+
});
|
|
87
|
+
// Comando: build
|
|
88
|
+
program
|
|
89
|
+
.command('build')
|
|
90
|
+
.description('Faz build do app')
|
|
91
|
+
.argument('<platform>', 'Plataforma: android ou ios')
|
|
92
|
+
.option('--release', 'Build de produção')
|
|
93
|
+
.action(async (platform, options) => {
|
|
94
|
+
await buildApp(platform, options.release);
|
|
95
|
+
});
|
|
96
|
+
// Comando: plugin
|
|
97
|
+
program
|
|
98
|
+
.command('plugin')
|
|
99
|
+
.description('Gerencia plugins nativos')
|
|
100
|
+
.argument('<action>', 'add ou list')
|
|
101
|
+
.argument('[plugin]', 'Nome do plugin (para add)')
|
|
102
|
+
.action(async (action, plugin) => {
|
|
103
|
+
if (action === 'add' && plugin) {
|
|
104
|
+
await addPlugin(plugin);
|
|
105
|
+
}
|
|
106
|
+
else if (action === 'list') {
|
|
107
|
+
await listPlugins();
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
// Funções auxiliares
|
|
111
|
+
async function initProject(name, appId, appName, webDir) {
|
|
112
|
+
const spinner = ora('Criando projeto FlutterBridge...').start();
|
|
113
|
+
try {
|
|
114
|
+
// Verifica se Flutter está instalado
|
|
115
|
+
try {
|
|
116
|
+
execSync('flutter --version', { stdio: 'ignore' });
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
spinner.fail('Flutter não encontrado. Instale o Flutter primeiro.');
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const projectPath = path.join(process.cwd(), name);
|
|
123
|
+
// Cria estrutura de diretórios
|
|
124
|
+
mkdirSync(projectPath, { recursive: true });
|
|
125
|
+
mkdirSync(path.join(projectPath, 'flutter_app'), { recursive: true });
|
|
126
|
+
// Cria flutterbridge.config.json
|
|
127
|
+
const config = {
|
|
128
|
+
appId,
|
|
129
|
+
appName,
|
|
130
|
+
webDir,
|
|
131
|
+
flutter: {
|
|
132
|
+
version: '3.0.0',
|
|
133
|
+
projectPath: 'flutter_app'
|
|
134
|
+
},
|
|
135
|
+
plugins: []
|
|
136
|
+
};
|
|
137
|
+
writeFileSync(path.join(projectPath, 'flutterbridge.config.json'), JSON.stringify(config, null, 2));
|
|
138
|
+
// Cria projeto Flutter
|
|
139
|
+
spinner.text = 'Criando projeto Flutter...';
|
|
140
|
+
execSync(`flutter create --org ${appId.split('.').slice(0, -1).join('.')} --project-name ${name.replace(/-/g, '_')} flutter_app`, {
|
|
141
|
+
cwd: projectPath,
|
|
142
|
+
stdio: 'pipe'
|
|
143
|
+
});
|
|
144
|
+
// Atualiza pubspec.yaml
|
|
145
|
+
const pubspecPath = path.join(projectPath, 'flutter_app', 'pubspec.yaml');
|
|
146
|
+
let pubspec = readFileSync(pubspecPath, 'utf-8');
|
|
147
|
+
// Adiciona dependências
|
|
148
|
+
// Encontra onde está "dependencies:" e adiciona depois
|
|
149
|
+
pubspec = pubspec.replace('dependencies:', `dependencies:
|
|
150
|
+
webview_flutter: ^4.4.2
|
|
151
|
+
path_provider: ^2.1.1
|
|
152
|
+
flutter_inappwebview: ^6.0.0`);
|
|
153
|
+
writeFileSync(pubspecPath, pubspec);
|
|
154
|
+
// Cria main.dart customizado
|
|
155
|
+
const mainDart = generateMainDart(appName);
|
|
156
|
+
writeFileSync(path.join(projectPath, 'flutter_app', 'lib', 'main.dart'), mainDart);
|
|
157
|
+
// Cria diretório para assets web
|
|
158
|
+
mkdirSync(path.join(projectPath, 'flutter_app', 'assets', 'web'), { recursive: true });
|
|
159
|
+
// Atualiza pubspec.yaml para incluir assets
|
|
160
|
+
pubspec += `
|
|
161
|
+
assets:
|
|
162
|
+
- assets/web/
|
|
163
|
+
`;
|
|
164
|
+
writeFileSync(pubspecPath, pubspec);
|
|
165
|
+
// Cria README
|
|
166
|
+
const readme = generateReadme(name, webDir);
|
|
167
|
+
writeFileSync(path.join(projectPath, 'README.md'), readme);
|
|
168
|
+
spinner.succeed(chalk.green(`Projeto ${name} criado com sucesso!`));
|
|
169
|
+
console.log(chalk.cyan('\nPróximos passos:'));
|
|
170
|
+
console.log(chalk.white(` cd ${name}`));
|
|
171
|
+
console.log(chalk.white(` # Faça o build do seu projeto web para ./${webDir}`));
|
|
172
|
+
console.log(chalk.white(` flutterbridge sync`));
|
|
173
|
+
console.log(chalk.white(` flutterbridge run android`));
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
spinner.fail('Erro ao criar projeto');
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async function addPlatform(platform) {
|
|
181
|
+
if (!['android', 'ios'].includes(platform)) {
|
|
182
|
+
console.error(chalk.red('Plataforma deve ser android ou ios'));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const spinner = ora(`Adicionando plataforma ${platform}...`).start();
|
|
186
|
+
try {
|
|
187
|
+
const config = await loadConfig();
|
|
188
|
+
const flutterPath = path.join(process.cwd(), config.flutter.projectPath);
|
|
189
|
+
// Flutter já cria android e ios por padrão, mas podemos recriar se necessário
|
|
190
|
+
console.log(chalk.green(`Plataforma ${platform} já está disponível no projeto Flutter`));
|
|
191
|
+
spinner.succeed();
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
spinner.fail('Erro ao adicionar plataforma');
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async function syncProject() {
|
|
199
|
+
const spinner = ora('Sincronizando projeto...').start();
|
|
200
|
+
try {
|
|
201
|
+
const config = await loadConfig();
|
|
202
|
+
const webDirPath = path.join(process.cwd(), config.webDir);
|
|
203
|
+
const assetsPath = path.join(process.cwd(), config.flutter.projectPath, 'assets', 'web');
|
|
204
|
+
// Verifica se diretório web existe
|
|
205
|
+
if (!existsSync(webDirPath)) {
|
|
206
|
+
spinner.fail(`Diretório ${config.webDir} não encontrado. Execute o build do seu projeto web primeiro.`);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
// Copia arquivos web para assets do Flutter
|
|
210
|
+
// limpa e copia (síncrono)
|
|
211
|
+
rmSync(assetsPath, { recursive: true, force: true });
|
|
212
|
+
mkdirSync(assetsPath, { recursive: true });
|
|
213
|
+
cpSync(webDirPath, assetsPath, { recursive: true });
|
|
214
|
+
// Executa flutter pub get
|
|
215
|
+
spinner.text = 'Instalando dependências Flutter...';
|
|
216
|
+
execSync('flutter pub get', {
|
|
217
|
+
cwd: path.join(process.cwd(), config.flutter.projectPath),
|
|
218
|
+
stdio: 'pipe'
|
|
219
|
+
});
|
|
220
|
+
spinner.succeed(chalk.green('Projeto sincronizado com sucesso!'));
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
spinner.fail('Erro ao sincronizar projeto');
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async function runApp(platform, device) {
|
|
228
|
+
const spinner = ora(`Executando app em ${platform}...`).start();
|
|
229
|
+
try {
|
|
230
|
+
await syncProject();
|
|
231
|
+
const config = await loadConfig();
|
|
232
|
+
const flutterPath = path.join(process.cwd(), config.flutter.projectPath);
|
|
233
|
+
const deviceFlag = device ? `-d ${device}` : '';
|
|
234
|
+
const cmd = `flutter run ${deviceFlag}`;
|
|
235
|
+
spinner.stop();
|
|
236
|
+
console.log(chalk.cyan(`Executando: ${cmd}`));
|
|
237
|
+
execSync(cmd, {
|
|
238
|
+
cwd: flutterPath,
|
|
239
|
+
stdio: 'inherit'
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
spinner.fail('Erro ao executar app');
|
|
244
|
+
throw error;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
async function buildApp(platform, release = false) {
|
|
248
|
+
const spinner = ora(`Fazendo build para ${platform}...`).start();
|
|
249
|
+
try {
|
|
250
|
+
await syncProject();
|
|
251
|
+
const config = await loadConfig();
|
|
252
|
+
const flutterPath = path.join(process.cwd(), config.flutter.projectPath);
|
|
253
|
+
const buildMode = release ? '--release' : '--debug';
|
|
254
|
+
const cmd = `flutter build ${platform === 'ios' ? 'ios' : 'apk'} ${buildMode}`;
|
|
255
|
+
spinner.text = `Executando: ${cmd}`;
|
|
256
|
+
execSync(cmd, {
|
|
257
|
+
cwd: flutterPath,
|
|
258
|
+
stdio: 'inherit'
|
|
259
|
+
});
|
|
260
|
+
spinner.succeed(chalk.green(`Build concluído!`));
|
|
261
|
+
if (platform === 'android') {
|
|
262
|
+
const apkPath = path.join(flutterPath, 'build', 'app', 'outputs', 'flutter-apk', release ? 'app-release.apk' : 'app-debug.apk');
|
|
263
|
+
console.log(chalk.cyan(`APK disponível em: ${apkPath}`));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
spinner.fail('Erro ao fazer build');
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
async function addPlugin(plugin) {
|
|
272
|
+
const spinner = ora(`Adicionando plugin ${plugin}...`).start();
|
|
273
|
+
try {
|
|
274
|
+
const config = await loadConfig();
|
|
275
|
+
const flutterPath = path.join(process.cwd(), config.flutter.projectPath);
|
|
276
|
+
execSync(`flutter pub add ${plugin}`, {
|
|
277
|
+
cwd: flutterPath,
|
|
278
|
+
stdio: 'pipe'
|
|
279
|
+
});
|
|
280
|
+
// Atualiza config
|
|
281
|
+
config.plugins.push(plugin);
|
|
282
|
+
writeFileSync(path.join(process.cwd(), 'flutterbridge.config.json'), JSON.stringify(config, null, 2));
|
|
283
|
+
spinner.succeed(chalk.green(`Plugin ${plugin} adicionado!`));
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
spinner.fail('Erro ao adicionar plugin');
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async function listPlugins() {
|
|
291
|
+
try {
|
|
292
|
+
const config = await loadConfig();
|
|
293
|
+
if (config.plugins.length === 0) {
|
|
294
|
+
console.log(chalk.yellow('Nenhum plugin instalado'));
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
console.log(chalk.cyan('Plugins instalados:'));
|
|
298
|
+
config.plugins.forEach((plugin) => {
|
|
299
|
+
console.log(chalk.white(` - ${plugin}`));
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
console.error(chalk.red('Erro ao listar plugins'));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
async function loadConfig() {
|
|
307
|
+
const configPath = path.join(process.cwd(), 'flutterbridge.config.json');
|
|
308
|
+
if (!existsSync(configPath)) {
|
|
309
|
+
throw new Error('Arquivo flutterbridge.config.json não encontrado. Execute "flutterbridge init" primeiro.');
|
|
310
|
+
}
|
|
311
|
+
return JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
312
|
+
}
|
|
313
|
+
function generateMainDart(appName) {
|
|
314
|
+
return `import 'package:flutter/material.dart';
|
|
315
|
+
import 'package:flutter/foundation.dart';
|
|
316
|
+
import 'package:flutter/services.dart';
|
|
317
|
+
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
|
318
|
+
|
|
319
|
+
void main() {
|
|
320
|
+
runApp(MyApp());
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
class MyApp extends StatelessWidget {
|
|
324
|
+
@override
|
|
325
|
+
Widget build(BuildContext context) {
|
|
326
|
+
return MaterialApp(
|
|
327
|
+
title: '${appName}',
|
|
328
|
+
theme: ThemeData(primarySwatch: Colors.blue),
|
|
329
|
+
home: WebViewPage(),
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
class WebViewPage extends StatefulWidget {
|
|
335
|
+
@override
|
|
336
|
+
_WebViewPageState createState() => _WebViewPageState();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
class _WebViewPageState extends State<WebViewPage> {
|
|
340
|
+
InAppWebViewController? webViewController;
|
|
341
|
+
double progress = 0;
|
|
342
|
+
|
|
343
|
+
@override
|
|
344
|
+
Widget build(BuildContext context) {
|
|
345
|
+
return Scaffold(
|
|
346
|
+
body: SafeArea(
|
|
347
|
+
child: Column(
|
|
348
|
+
children: [
|
|
349
|
+
progress < 1.0
|
|
350
|
+
? LinearProgressIndicator(value: progress)
|
|
351
|
+
: Container(),
|
|
352
|
+
Expanded(
|
|
353
|
+
child: InAppWebView(
|
|
354
|
+
initialSettings: InAppWebViewSettings(
|
|
355
|
+
javaScriptEnabled: true,
|
|
356
|
+
allowFileAccessFromFileURLs: true,
|
|
357
|
+
allowUniversalAccessFromFileURLs: true,
|
|
358
|
+
mediaPlaybackRequiresUserGesture: false,
|
|
359
|
+
useOnLoadResource: true,
|
|
360
|
+
),
|
|
361
|
+
initialFile: 'assets/web/index.html',
|
|
362
|
+
onWebViewCreated: (controller) {
|
|
363
|
+
webViewController = controller;
|
|
364
|
+
|
|
365
|
+
if (!kIsWeb) {
|
|
366
|
+
controller.addJavaScriptHandler(
|
|
367
|
+
handlerName: 'FlutterBridge',
|
|
368
|
+
callback: (args) {
|
|
369
|
+
print('Mensagem do JS: \$args');
|
|
370
|
+
return {'response': 'Recebido pelo Flutter', 'data': args};
|
|
371
|
+
},
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
onProgressChanged: (controller, progress) {
|
|
376
|
+
setState(() {
|
|
377
|
+
this.progress = progress / 100;
|
|
378
|
+
});
|
|
379
|
+
},
|
|
380
|
+
onLoadStop: (controller, url) {
|
|
381
|
+
setState(() {
|
|
382
|
+
progress = 1.0;
|
|
383
|
+
});
|
|
384
|
+
},
|
|
385
|
+
onLoadResource: (controller, resource) {
|
|
386
|
+
print('Carregando: \${resource.url}');
|
|
387
|
+
},
|
|
388
|
+
onLoadResourceWithCustomScheme: (controller, request) async {
|
|
389
|
+
// Intercepta requests personalizados
|
|
390
|
+
var url = request.url.toString();
|
|
391
|
+
print('Custom scheme: \$url');
|
|
392
|
+
|
|
393
|
+
return null;
|
|
394
|
+
},
|
|
395
|
+
shouldOverrideUrlLoading: (controller, navigationAction) async {
|
|
396
|
+
var uri = navigationAction.request.url!;
|
|
397
|
+
var url = uri.toString();
|
|
398
|
+
|
|
399
|
+
print('Navegando para: \$url');
|
|
400
|
+
|
|
401
|
+
// Se for navegação interna (mesma origem), permite
|
|
402
|
+
if (url.startsWith('file://') ||
|
|
403
|
+
url.startsWith('about:') ||
|
|
404
|
+
url.contains('assets/web/')) {
|
|
405
|
+
return NavigationActionPolicy.ALLOW;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Se for link externo (http/https), abre no navegador
|
|
409
|
+
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
410
|
+
// Aqui você pode usar url_launcher se quiser abrir no browser
|
|
411
|
+
print('Link externo detectado: \$url');
|
|
412
|
+
return NavigationActionPolicy.ALLOW;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return NavigationActionPolicy.ALLOW;
|
|
416
|
+
},
|
|
417
|
+
onConsoleMessage: (controller, consoleMessage) {
|
|
418
|
+
print('Console [\${consoleMessage.messageLevel}]: \${consoleMessage.message}');
|
|
419
|
+
},
|
|
420
|
+
onLoadError: (controller, url, code, message) {
|
|
421
|
+
print('Erro ao carregar \$url: \$message (código: \$code)');
|
|
422
|
+
},
|
|
423
|
+
onLoadHttpError: (controller, url, statusCode, description) {
|
|
424
|
+
print('Erro HTTP ao carregar \$url: \$statusCode - \$description');
|
|
425
|
+
},
|
|
426
|
+
),
|
|
427
|
+
),
|
|
428
|
+
],
|
|
429
|
+
),
|
|
430
|
+
),
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
`;
|
|
435
|
+
}
|
|
436
|
+
function generateReadme(name, webDir) {
|
|
437
|
+
return `# ${name}
|
|
438
|
+
|
|
439
|
+
Projeto criado com FlutterBridge CLI
|
|
440
|
+
|
|
441
|
+
## Estrutura do Projeto
|
|
442
|
+
|
|
443
|
+
\`\`\`
|
|
444
|
+
${name}/
|
|
445
|
+
├── flutterbridge.config.json # Configuração do FlutterBridge
|
|
446
|
+
├── flutter_app/ # Projeto Flutter
|
|
447
|
+
│ ├── lib/
|
|
448
|
+
│ │ └── main.dart # App Flutter principal
|
|
449
|
+
│ ├── assets/
|
|
450
|
+
│ │ └── web/ # Arquivos web copiados aqui
|
|
451
|
+
│ └── pubspec.yaml
|
|
452
|
+
└── ${webDir}/ # Build do seu projeto web (React/Vue/Angular)
|
|
453
|
+
\`\`\`
|
|
454
|
+
|
|
455
|
+
## Comandos Disponíveis
|
|
456
|
+
|
|
457
|
+
### Sincronizar projeto web com Flutter
|
|
458
|
+
\`\`\`bash
|
|
459
|
+
flutterbridge sync
|
|
460
|
+
\`\`\`
|
|
461
|
+
|
|
462
|
+
### Executar em emulador/dispositivo
|
|
463
|
+
\`\`\`bash
|
|
464
|
+
flutterbridge run android
|
|
465
|
+
flutterbridge run ios
|
|
466
|
+
\`\`\`
|
|
467
|
+
|
|
468
|
+
### Build de produção
|
|
469
|
+
\`\`\`bash
|
|
470
|
+
flutterbridge build android --release
|
|
471
|
+
flutterbridge build ios --release
|
|
472
|
+
\`\`\`
|
|
473
|
+
|
|
474
|
+
### Adicionar plugins Flutter
|
|
475
|
+
\`\`\`bash
|
|
476
|
+
flutterbridge plugin add camera
|
|
477
|
+
flutterbridge plugin add geolocator
|
|
478
|
+
flutterbridge plugin list
|
|
479
|
+
\`\`\`
|
|
480
|
+
|
|
481
|
+
## Comunicação JavaScript <-> Flutter
|
|
482
|
+
|
|
483
|
+
No seu código JavaScript, você pode se comunicar com o Flutter:
|
|
484
|
+
|
|
485
|
+
\`\`\`javascript
|
|
486
|
+
// Enviar mensagem para Flutter
|
|
487
|
+
window.flutter_inappwebview.callHandler('FlutterBridge', {
|
|
488
|
+
action: 'getData'
|
|
489
|
+
}).then(function(result) {
|
|
490
|
+
console.log('Resposta do Flutter:', result);
|
|
491
|
+
});
|
|
492
|
+
\`\`\`
|
|
493
|
+
|
|
494
|
+
## Workflow de Desenvolvimento
|
|
495
|
+
|
|
496
|
+
1. Desenvolva seu app web normalmente (React/Vue/Angular)
|
|
497
|
+
2. Faça o build: \`npm run build\`
|
|
498
|
+
3. Sincronize: \`flutterbridge sync\`
|
|
499
|
+
4. Execute: \`flutterbridge run android\`
|
|
500
|
+
|
|
501
|
+
## Requisitos
|
|
502
|
+
|
|
503
|
+
- Flutter SDK instalado
|
|
504
|
+
- Node.js 16+
|
|
505
|
+
- Android Studio (para Android)
|
|
506
|
+
- Xcode (para iOS, apenas macOS)
|
|
507
|
+
`;
|
|
508
|
+
}
|
|
509
|
+
// Executa o programa
|
|
510
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "flutterbridge",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "CLI para fazer apps Android via WebView do Flutter",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"flutter",
|
|
9
|
+
"cli",
|
|
10
|
+
"mobile",
|
|
11
|
+
"capacitor",
|
|
12
|
+
"webview"
|
|
13
|
+
],
|
|
14
|
+
"bin": {
|
|
15
|
+
"flutterbridge": "dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"prepublishOnly": "npm run build",
|
|
23
|
+
"dev": "tsx src/index.ts",
|
|
24
|
+
"test": "node dist/index.js --help"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"chalk": "^5.3.0",
|
|
28
|
+
"commander": "^11.1.0",
|
|
29
|
+
"fs-extra": "^10.1.0",
|
|
30
|
+
"inquirer": "^9.2.12",
|
|
31
|
+
"ora": "^8.0.1"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/fs-extra": "^11.0.4",
|
|
35
|
+
"@types/inquirer": "^9.0.7",
|
|
36
|
+
"@types/node": "^20.10.0",
|
|
37
|
+
"tsx": "^4.7.0",
|
|
38
|
+
"typescript": "^5.3.3"
|
|
39
|
+
}
|
|
40
|
+
}
|