ui/lib/modules/settings/settings_screen.dart (451 lines of code) (raw):
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import 'package:cloud_provision_shared/services/models/project.dart';
import 'package:cloudprovision/modules/settings/application/project_service.dart';
import 'package:cloudprovision/modules/settings/data/settings_repository.dart';
import 'package:cloudprovision/modules/settings/models/git_settings.dart';
import 'package:cloudprovision/modules/settings/project_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../app_appbar.dart';
import '../../app_drawer.dart';
import '../../utils/environment.dart';
import 'data/project_provider.dart';
class SettingsScreen extends ConsumerWidget {
TextStyle textStyle = GoogleFonts.openSans(
fontSize: 14,
color: Colors.black,
fontWeight: FontWeight.w600,
letterSpacing: 0.8);
final _keyGitForm = GlobalKey<FormState>();
late String _customerTemplateGitRepository = "";
late String _instanceGitUsername = "";
late String _instanceGitToken = "";
late String _gcpApiKey = "";
final String fieldIsRequired = "This field is required";
final String selectProjectText = "Select a project";
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: App_AppBar(),
drawer: AppDrawer(),
body: _getBody(context, ref),
);
}
Widget _getBody(BuildContext context, WidgetRef ref) {
final settings = ref.watch(gitSettingsProvider);
return settings.when(
loading: () => Text('Loading...'),
error: (err, stack) => Text('Error: $err'),
data: (gitSettings) {
return Container(
padding: EdgeInsets.all(24),
child: DefaultTabController(
length: 2,
child: Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SelectableText(
"Workspace settings",
style: GoogleFonts.openSans(
fontSize: 32,
color: Color(0xFF1b3a57),
fontWeight: FontWeight.w600,
),
),
TabBar(
isScrollable: true,
onTap: (int index) {
},
tabs: [
Container(
child: Tab(
child: Text(
"Workbench Config",
style: textStyle,
)),
),
Container(
child: Tab(
child: Text(
"Git Config",
style: textStyle,
)),
),
],
),
Expanded(
child: TabBarView(
children: [
_generalTab(context),
_apisTab(context, gitSettings, ref),
],
),
),
],
),
),
),
);
});
}
_generalTab(BuildContext context) {
return SingleChildScrollView(
child: Container(
child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: double.infinity,
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 20),
Container(width: 800, child: Text("Your workspace"))
])),
),
SizedBox(height: 10),
Center(
child: Card(
elevation: 5,
child: SizedBox(
width: 800,
height: 500,
child: Theme(
data: Theme.of(context).copyWith(
dividerColor: Colors.white,
),
child: DataTable(
headingRowHeight: 10,
dividerThickness: 0,
dataRowHeight: 40,
columnSpacing: 0,
columns: [
DataColumn(label: Text("")),
DataColumn(label: Text("")),
],
rows: [
DataRow(
cells: [
DataCell(ConstrainedBox(
constraints: BoxConstraints(maxWidth: 200),
child: Text(
'Workspace name',
style: TextStyle(color: Colors.black54),
))),
DataCell(Row(
children: [
Text('Developer Workbench'),
SizedBox(width: 5),
IconButton(
icon: new Icon(
Icons.edit,
color: Colors.black54,
),
onPressed: () {},
),
],
)),
],
),
DataRow(
cells: <DataCell>[
DataCell(ConstrainedBox(
constraints: BoxConstraints(maxWidth: 200),
child: Text(
'Project ID',
style: TextStyle(color: Colors.black54),
))),
DataCell(Text(Environment.getProjectId())),
],
),
DataRow(
cells: <DataCell>[
DataCell(ConstrainedBox(
constraints: BoxConstraints(maxWidth: 200),
child: Text(
'Location',
style: TextStyle(color: Colors.black54),
))),
DataCell(Text(dotenv.get('DEFAULT_REGION'))),
],
),
DataRow(
cells: <DataCell>[
DataCell(ConstrainedBox(
constraints: BoxConstraints(maxWidth: 200),
child: Text(
'Catalog Repository',
style: TextStyle(color: Colors.black54),
))),
DataCell(
TextButton(
onPressed: () async {
final Uri _url = Uri.parse(dotenv.get("GCP_CATALOG_URL"));
if (!await launchUrl(_url)) {
throw 'Could not launch $_url';
}
},
child: Text("GitHub"),
)
)
],
),
],
),
),
),
),
),
SizedBox(height: 10),
Container(
width: double.infinity,
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: new Icon(
Icons.delete,
color: Colors.black54,
),
onPressed: () {},
),
Text("Delete workspace"),
],
)
],
)),
),
],
),
),
);
}
_apisTab(BuildContext context, GitSettings settings, WidgetRef ref) {
_instanceGitUsername = settings.instanceGitUsername;
_instanceGitToken = settings.instanceGitToken;
_gcpApiKey = settings.gcpApiKey;
_customerTemplateGitRepository = settings.customerTemplateGitRepository;
TextEditingController _gcpApiKeyController = TextEditingController();
TextEditingController _urlCustomerRepoController = TextEditingController();
TextEditingController _usernameController = TextEditingController();
TextEditingController _tokenController = TextEditingController();
_urlCustomerRepoController.text = settings.customerTemplateGitRepository;
_usernameController.text = settings.instanceGitUsername;
_tokenController.text = settings.instanceGitToken;
_gcpApiKeyController.text = settings.gcpApiKey;
var projectInitialValue = settings.targetProject == ""
? selectProjectText
: settings.targetProject;
final projectsList = ref.watch(projectsProvider);
return SingleChildScrollView(
child: Container(
child: Column(
children: [
Container(
width: double.infinity,
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 20),
])),
),
SizedBox(height: 10),
Container(
child: Center(
child: Card(
elevation: 5,
child: SizedBox(
width: 800,
height: 650,
child: Container(
child: Form(
key: _keyGitForm,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: SizedBox(
width: 200,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 700,
),
child: projectsList.when(
loading: () => Container(),
error: (err, stack) => Text("Failed to load projects. ${err}"),
data: (projects) {
if (projects.isNotEmpty) {
return ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 700,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(selectProjectText),
ProjectWidget(ref, projectInitialValue),
],
),
);
} else {
return Container();
}
}),
),
],
),
SizedBox(height: 30),
TextFormField(
controller: _usernameController,
decoration: InputDecoration(
labelText: "Git Username",
),
validator: (value) {
if (value == null || value.isEmpty) {
return fieldIsRequired;
}
return null;
},
onChanged: (val) {
_instanceGitUsername = val;
}),
SizedBox(height: 30),
TextFormField(
obscureText: true,
enableSuggestions: false,
autocorrect: false,
controller: _tokenController,
decoration: InputDecoration(
labelText: "Git Personal Access Token",
),
validator: (value) {
if (value == null || value.isEmpty) {
return fieldIsRequired;
}
return null;
},
onChanged: (val) {
_instanceGitToken = val;
}),
SizedBox(height: 30),
TextFormField(
obscureText: true,
enableSuggestions: false,
autocorrect: false,
controller: _gcpApiKeyController,
decoration: InputDecoration(
labelText:
"GCP API Key (Restrictions: Cloud Build API)",
),
validator: (value) {
if (value == null || value.isEmpty) {
return fieldIsRequired;
}
return null;
},
onChanged: (val) {
_gcpApiKey = val;
}),
SizedBox(height: 30),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor:
Theme.of(context).primaryColor,
),
child: const Text('Update configuration'),
onPressed: () =>
{_updateGitConfiguration(context, ref)},
),
SizedBox(height: 30),
_enableProjectSection(context, ref),
],
),
),
),
),
),
),
),
),
),
SizedBox(height: 10),
Container(
width: double.infinity,
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: new Icon(
Icons.delete,
color: Colors.black54,
),
onPressed: () {},
),
Text("Delete configuration"),
],
)
],
)),
),
],
),
),
);
}
_updateGitConfiguration(BuildContext context, WidgetRef ref) {
if (!_keyGitForm.currentState!.validate()) {
return;
}
final settingRepo = ref.read(settingsRepositoryProvider);
Project project = ref.read(projectProvider);
GitSettings gitSettings = GitSettings(
_instanceGitUsername,
_instanceGitToken,
_customerTemplateGitRepository,
_gcpApiKey,
project.projectId,
);
settingRepo.updateGitSettings(gitSettings);
// TODO remove this and switch to ref.read(projectProvider) in _enableProjectSection
ref.read(projectDropdownProvider.notifier).state = project.projectId;
ref.invalidate(gitSettingsProvider);
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
behavior: SnackBarBehavior.floating,
margin: EdgeInsets.all(25.0),
backgroundColor: Theme.of(context).primaryColor,
content: Text("Configuration was updated."),
));
}
_enableProjectSection(BuildContext context, WidgetRef ref) {
// Project project = ref.watch(projectProvider);
// TODO switch to ref.watch(projectProvider)
String projectId = ref.watch(projectDropdownProvider);
return projectId == "null" || projectId == "Select a project" ?
Text("Select a project from the dropdown to enable APIs and grant IAM roles") :
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor:
Theme.of(context).primaryColor,
),
child: Text("Bootstrap Project ${projectId}"),
onPressed: () async
{
Project project = ref.read(projectProvider);
await ref.read(projectServiceProvider).bootstrapTargetProject(project);
},
);
}
}