mirror of
https://github.com/go-vikunja/app
synced 2024-06-02 18:49:47 +00:00
feat: add web support
This commit is contained in:
parent
1d538d6816
commit
0e29b6620d
26
.metadata
26
.metadata
|
@ -4,5 +4,27 @@
|
||||||
# This file should be version controlled and should not be manually edited.
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
version:
|
version:
|
||||||
revision: 3b309bda072a6b326e8aa4591a5836af600923ce
|
revision: "300451adae589accbece3490f4396f10bdf15e6e"
|
||||||
channel: beta
|
channel: "stable"
|
||||||
|
|
||||||
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: 300451adae589accbece3490f4396f10bdf15e6e
|
||||||
|
base_revision: 300451adae589accbece3490f4396f10bdf15e6e
|
||||||
|
- platform: web
|
||||||
|
create_revision: 300451adae589accbece3490f4396f10bdf15e6e
|
||||||
|
base_revision: 300451adae589accbece3490f4396f10bdf15e6e
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||||
|
|
29
.vscode/launch.json
vendored
29
.vscode/launch.json
vendored
|
@ -1,14 +1,19 @@
|
||||||
{
|
{
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "Flutter",
|
"name": "Flutter (Chromium)",
|
||||||
"request": "launch",
|
"type": "dart",
|
||||||
"type": "dart",
|
"request": "launch",
|
||||||
"flutterMode": "debug",
|
"program": "lib/main.dart",
|
||||||
"args": [
|
"args": ["-d", "chrome", "--flavor", "main"],
|
||||||
"--flavor",
|
"deviceId": "chrome",
|
||||||
"main"
|
},
|
||||||
],
|
{
|
||||||
}
|
"name": "Flutter",
|
||||||
]
|
"request": "launch",
|
||||||
|
"type": "dart",
|
||||||
|
"flutterMode": "debug",
|
||||||
|
"args": ["--flavor", "main"]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../global.dart';
|
import '../global.dart';
|
||||||
import '../models/bucket.dart';
|
import '../models/bucket.dart';
|
||||||
import '../models/list.dart';
|
|
||||||
import '../models/project.dart';
|
import '../models/project.dart';
|
||||||
import '../pages/list/list.dart';
|
import '../pages/list/list.dart';
|
||||||
import '../stores/project_store.dart';
|
import '../stores/project_store.dart';
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:developer' as dev;
|
import 'dart:developer' as dev;
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:vikunja_app/api/bucket_implementation.dart';
|
import 'package:vikunja_app/api/bucket_implementation.dart';
|
||||||
|
@ -89,6 +90,9 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
||||||
late String currentTimeZone;
|
late String currentTimeZone;
|
||||||
|
|
||||||
void updateWorkmanagerDuration() {
|
void updateWorkmanagerDuration() {
|
||||||
|
if (kIsWeb) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Workmanager().cancelAll().then((value) {
|
Workmanager().cancelAll().then((value) {
|
||||||
settingsManager.getWorkmanagerDuration().then((duration) {
|
settingsManager.getWorkmanagerDuration().then((duration) {
|
||||||
if (duration.inMinutes > 0) {
|
if (duration.inMinutes > 0) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
@ -34,6 +35,9 @@ class IgnoreCertHttpOverrides extends HttpOverrides {
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
void callbackDispatcher() {
|
void callbackDispatcher() {
|
||||||
|
if (kIsWeb) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Workmanager().executeTask((task, inputData) async {
|
Workmanager().executeTask((task, inputData) async {
|
||||||
print(
|
print(
|
||||||
"Native called background task: $task"); //simpleTask will be emitted here.
|
"Native called background task: $task"); //simpleTask will be emitted here.
|
||||||
|
@ -95,18 +99,31 @@ void main() async {
|
||||||
Permission.notification.request();
|
Permission.notification.request();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await FlutterDownloader.initialize();
|
try {
|
||||||
Workmanager().initialize(callbackDispatcher, isInDebugMode: false);
|
if (!kIsWeb) {
|
||||||
|
await FlutterDownloader.initialize();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to initialize downloader: $e");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (!kIsWeb) {
|
||||||
|
Workmanager().initialize(callbackDispatcher, isInDebugMode: false);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to initialize workmanager: $e");
|
||||||
|
}
|
||||||
runApp(VikunjaGlobal(
|
runApp(VikunjaGlobal(
|
||||||
child: new VikunjaApp(
|
child: new VikunjaApp(
|
||||||
home: HomePage(),
|
home: HomePage(),
|
||||||
key: UniqueKey(),
|
key: UniqueKey(),
|
||||||
navkey: globalNavigatorKey,
|
navkey: globalNavigatorKey,
|
||||||
),
|
),
|
||||||
login: new VikunjaApp(
|
login: new VikunjaApp(
|
||||||
home: LoginPage(),
|
home: LoginPage(),
|
||||||
key: UniqueKey(),
|
key: UniqueKey(),
|
||||||
)));
|
),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
final ValueNotifier<bool> updateTheme = ValueNotifier(false);
|
final ValueNotifier<bool> updateTheme = ValueNotifier(false);
|
||||||
|
@ -118,38 +135,41 @@ class VikunjaApp extends StatelessWidget {
|
||||||
const VikunjaApp({Key? key, required this.home, this.navkey})
|
const VikunjaApp({Key? key, required this.home, this.navkey})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
|
Future<ThemeData> getThemedata() async {
|
||||||
|
FlutterThemeMode themeMode = FlutterThemeMode.light;
|
||||||
|
try {
|
||||||
|
SettingsManager manager = SettingsManager(new FlutterSecureStorage());
|
||||||
|
themeMode = await manager.getThemeMode();
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to get theme mode: $e");
|
||||||
|
}
|
||||||
|
switch (themeMode) {
|
||||||
|
case FlutterThemeMode.dark:
|
||||||
|
return buildVikunjaDarkTheme();
|
||||||
|
case FlutterThemeMode.materialYouLight:
|
||||||
|
return buildVikunjaMaterialLightTheme();
|
||||||
|
case FlutterThemeMode.materialYouDark:
|
||||||
|
return buildVikunjaMaterialDarkTheme();
|
||||||
|
default:
|
||||||
|
return buildVikunjaTheme();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
SettingsManager manager = SettingsManager(new FlutterSecureStorage());
|
|
||||||
|
|
||||||
return new ValueListenableBuilder(
|
return new ValueListenableBuilder(
|
||||||
valueListenable: updateTheme,
|
valueListenable: updateTheme,
|
||||||
builder: (_, mode, __) {
|
builder: (_, mode, __) {
|
||||||
updateTheme.value = false;
|
|
||||||
FlutterThemeMode themeMode = FlutterThemeMode.system;
|
|
||||||
Future<ThemeData> theme = manager.getThemeMode().then((value) {
|
|
||||||
themeMode = value;
|
|
||||||
switch (value) {
|
|
||||||
case FlutterThemeMode.dark:
|
|
||||||
return buildVikunjaDarkTheme();
|
|
||||||
case FlutterThemeMode.materialYouLight:
|
|
||||||
return buildVikunjaMaterialLightTheme();
|
|
||||||
case FlutterThemeMode.materialYouDark:
|
|
||||||
return buildVikunjaMaterialDarkTheme();
|
|
||||||
default:
|
|
||||||
return buildVikunjaTheme();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return FutureBuilder<ThemeData>(
|
return FutureBuilder<ThemeData>(
|
||||||
future: theme,
|
future: getThemedata(),
|
||||||
builder: (BuildContext context, AsyncSnapshot<ThemeData> data) {
|
builder: (BuildContext context, AsyncSnapshot<ThemeData> data) {
|
||||||
if (data.hasData) {
|
if (data.hasData) {
|
||||||
return new DynamicColorBuilder(
|
return new DynamicColorBuilder(
|
||||||
builder: (lightTheme, darkTheme) {
|
builder: (lightTheme, darkTheme) {
|
||||||
ThemeData? themeData = data.data;
|
ThemeData? themeData = data.data;
|
||||||
if (themeMode == FlutterThemeMode.materialYouLight)
|
if (data.data == FlutterThemeMode.materialYouLight)
|
||||||
themeData = themeData?.copyWith(colorScheme: lightTheme);
|
themeData = themeData?.copyWith(colorScheme: lightTheme);
|
||||||
else if (themeMode == FlutterThemeMode.materialYouDark)
|
else if (data.data == FlutterThemeMode.materialYouDark)
|
||||||
themeData = themeData?.copyWith(colorScheme: darkTheme);
|
themeData = themeData?.copyWith(colorScheme: darkTheme);
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'Vikunja',
|
title: 'Vikunja',
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import 'dart:ffi';
|
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
|
||||||
import 'package:vikunja_app/global.dart';
|
import 'package:vikunja_app/global.dart';
|
||||||
import 'package:vikunja_app/models/list.dart';
|
import 'package:vikunja_app/models/list.dart';
|
||||||
import 'package:vikunja_app/theme/button.dart';
|
import 'package:vikunja_app/theme/button.dart';
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import 'dart:ffi';
|
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:vikunja_app/global.dart';
|
import 'package:vikunja_app/global.dart';
|
||||||
|
|
|
@ -8,10 +8,8 @@ import 'package:vikunja_app/components/AddDialog.dart';
|
||||||
import 'package:vikunja_app/components/KanbanWidget.dart';
|
import 'package:vikunja_app/components/KanbanWidget.dart';
|
||||||
import 'package:vikunja_app/components/TaskTile.dart';
|
import 'package:vikunja_app/components/TaskTile.dart';
|
||||||
import 'package:vikunja_app/global.dart';
|
import 'package:vikunja_app/global.dart';
|
||||||
import 'package:vikunja_app/models/list.dart';
|
|
||||||
import 'package:vikunja_app/models/task.dart';
|
import 'package:vikunja_app/models/task.dart';
|
||||||
import 'package:vikunja_app/models/bucket.dart';
|
import 'package:vikunja_app/models/bucket.dart';
|
||||||
import 'package:vikunja_app/pages/list/list_edit.dart';
|
|
||||||
import 'package:vikunja_app/pages/list/task_edit.dart';
|
import 'package:vikunja_app/pages/list/task_edit.dart';
|
||||||
import 'package:vikunja_app/pages/project/project_edit.dart';
|
import 'package:vikunja_app/pages/project/project_edit.dart';
|
||||||
|
|
||||||
|
|
|
@ -324,10 +324,10 @@ class SettingsManager {
|
||||||
key: "workmanager-duration", value: duration.inMinutes.toString());
|
key: "workmanager-duration", value: duration.inMinutes.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<String>?> getPastServers() {
|
Future<List<String>?> getPastServers() async {
|
||||||
return _storage
|
String jsonString = await _storage.read(key: "recent-servers") ?? "[]";
|
||||||
.read(key: "recent-servers")
|
List<dynamic> server = jsonDecode(jsonString);
|
||||||
.then((value) => (jsonDecode(value!) as List<dynamic>).cast<String>());
|
return server.map((e) => e as String).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setPastServers(List<String>? server) {
|
Future<void> setPastServers(List<String>? server) {
|
||||||
|
|
BIN
web/favicon.png
Normal file
BIN
web/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 917 B |
BIN
web/icons/Icon-192.png
Normal file
BIN
web/icons/Icon-192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
BIN
web/icons/Icon-512.png
Normal file
BIN
web/icons/Icon-512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
BIN
web/icons/Icon-maskable-192.png
Normal file
BIN
web/icons/Icon-maskable-192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.5 KiB |
BIN
web/icons/Icon-maskable-512.png
Normal file
BIN
web/icons/Icon-maskable-512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
59
web/index.html
Normal file
59
web/index.html
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<!--
|
||||||
|
If you are serving your web app in a path other than the root, change the
|
||||||
|
href value below to reflect the base path you are serving from.
|
||||||
|
|
||||||
|
The path provided below has to start and end with a slash "/" in order for
|
||||||
|
it to work correctly.
|
||||||
|
|
||||||
|
For more details:
|
||||||
|
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
||||||
|
|
||||||
|
This is a placeholder for base href that will be replaced by the value of
|
||||||
|
the `--base-href` argument provided to `flutter build`.
|
||||||
|
-->
|
||||||
|
<base href="$FLUTTER_BASE_HREF">
|
||||||
|
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||||
|
<meta name="description" content="A new Flutter project.">
|
||||||
|
|
||||||
|
<!-- iOS meta tags & icons -->
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="vikunja_app">
|
||||||
|
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||||
|
|
||||||
|
<!-- Favicon -->
|
||||||
|
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||||
|
|
||||||
|
<title>vikunja_app</title>
|
||||||
|
<link rel="manifest" href="manifest.json">
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// The value below is injected by flutter build, do not touch.
|
||||||
|
const serviceWorkerVersion = null;
|
||||||
|
</script>
|
||||||
|
<!-- This script adds the flutter initialization JS code -->
|
||||||
|
<script src="flutter.js" defer></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
window.addEventListener('load', function(ev) {
|
||||||
|
// Download main.dart.js
|
||||||
|
_flutter.loader.loadEntrypoint({
|
||||||
|
serviceWorker: {
|
||||||
|
serviceWorkerVersion: serviceWorkerVersion,
|
||||||
|
},
|
||||||
|
onEntrypointLoaded: function(engineInitializer) {
|
||||||
|
engineInitializer.initializeEngine().then(function(appRunner) {
|
||||||
|
appRunner.runApp();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
35
web/manifest.json
Normal file
35
web/manifest.json
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"name": "vikunja_app",
|
||||||
|
"short_name": "vikunja_app",
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#0175C2",
|
||||||
|
"theme_color": "#0175C2",
|
||||||
|
"description": "A new Flutter project.",
|
||||||
|
"orientation": "portrait-primary",
|
||||||
|
"prefer_related_applications": false,
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-maskable-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-maskable-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user