diff --git a/assets/fonts/Metropolis-Regular.otf b/assets/fonts/Metropolis-Regular.otf new file mode 100644 index 0000000..737760b Binary files /dev/null and b/assets/fonts/Metropolis-Regular.otf differ diff --git a/lib/main.dart b/lib/main.dart index d62f6f1..a2eb7cc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:obtainium/pages/apps.dart'; +import 'package:obtainium/pages/home.dart'; import 'package:obtainium/services/apps_provider.dart'; import 'package:obtainium/services/source_service.dart'; import 'package:provider/provider.dart'; import 'package:workmanager/workmanager.dart'; +import 'package:dynamic_color/dynamic_color.dart'; void backgroundUpdateCheck() { Workmanager().executeTask((task, inputData) async { @@ -36,6 +37,7 @@ void main() async { await Workmanager().registerPeriodicTask( 'update-apps-task', 'backgroundUpdateCheck', frequency: const Duration(minutes: 15), + initialDelay: const Duration(minutes: 15), constraints: Constraints(networkType: NetworkType.connected)); runApp(MultiProvider( providers: [ChangeNotifierProvider(create: (context) => AppsProvider())], @@ -43,16 +45,35 @@ void main() async { )); } +var defaultThemeColour = const Color(0xFF69F0AE); + class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { - return MaterialApp( - title: 'Obtainium', - theme: ThemeData( - primarySwatch: Colors.blue, - ), - home: const AppsPage()); + return DynamicColorBuilder( + builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) { + ColorScheme lightColorScheme; + ColorScheme darkColorScheme; + if (lightDynamic != null && darkDynamic != null) { + lightColorScheme = lightDynamic.harmonized(); + darkColorScheme = darkDynamic.harmonized(); + } else { + lightColorScheme = ColorScheme.fromSeed(seedColor: defaultThemeColour); + darkColorScheme = ColorScheme.fromSeed( + seedColor: defaultThemeColour, brightness: Brightness.dark); + } + + return MaterialApp( + title: 'Obtainium', + theme: ThemeData( + useMaterial3: true, + colorScheme: lightColorScheme, + fontFamily: 'Metropolis'), + darkTheme: + ThemeData(useMaterial3: true, colorScheme: darkColorScheme), + home: const HomePage()); + }); } } diff --git a/lib/pages/add_app.dart b/lib/pages/add_app.dart index 92bea22..01b7560 100644 --- a/lib/pages/add_app.dart +++ b/lib/pages/add_app.dart @@ -18,78 +18,72 @@ class _AddAppPageState extends State { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Obtainium - Add App'), - ), - body: Center( - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const Spacer(), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: TextFormField( - decoration: const InputDecoration( - border: OutlineInputBorder(), - hintText: 'https://github.com/Author/Project', - helperText: 'Enter the App source URL'), - controller: urlInputController, - validator: (value) { - if (value == null || - value.isEmpty || - Uri.tryParse(value) == null) { - return 'Please enter a supported source URL'; - } - return null; - }, - )), - Padding( - padding: - const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), - child: ElevatedButton( - onPressed: gettingAppInfo - ? null - : () { - if (_formKey.currentState!.validate()) { + return Center( + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Spacer(), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: TextFormField( + decoration: const InputDecoration( + hintText: 'https://github.com/Author/Project', + helperText: 'Enter the App source URL'), + controller: urlInputController, + validator: (value) { + if (value == null || + value.isEmpty || + Uri.tryParse(value) == null) { + return 'Please enter a supported source URL'; + } + return null; + }, + )), + Padding( + padding: + const EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0), + child: ElevatedButton( + onPressed: gettingAppInfo + ? null + : () { + if (_formKey.currentState!.validate()) { + setState(() { + gettingAppInfo = true; + }); + SourceService() + .getApp(urlInputController.value.text) + .then((app) { + var appsProvider = context.read(); + if (appsProvider.apps.containsKey(app.id)) { + throw 'App already added'; + } + appsProvider.saveApp(app).then((_) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + AppPage(appId: app.id))); + }); + }).catchError((e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(e.toString())), + ); + }).whenComplete(() { setState(() { - gettingAppInfo = true; + gettingAppInfo = false; }); - SourceService() - .getApp(urlInputController.value.text) - .then((app) { - var appsProvider = context.read(); - if (appsProvider.apps.containsKey(app.id)) { - throw 'App already added'; - } - appsProvider.saveApp(app).then((_) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - AppPage(appId: app.id))); - }); - }).catchError((e) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(e.toString())), - ); - }).whenComplete(() { - setState(() { - gettingAppInfo = false; - }); - }); - } - }, - child: const Text('Add'), - ), + }); + } + }, + child: const Text('Add'), ), - const Spacer(), - if (gettingAppInfo) const LinearProgressIndicator(), - ], - ), - )), - ); + ), + const Spacer(), + if (gettingAppInfo) const LinearProgressIndicator(), + ], + ), + )); } } diff --git a/lib/pages/app.dart b/lib/pages/app.dart index 78f120f..0586257 100644 --- a/lib/pages/app.dart +++ b/lib/pages/app.dart @@ -38,7 +38,7 @@ class _AppPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Expanded( - child: OutlinedButton( + child: ElevatedButton( onPressed: (app?.installedVersion == null || appsProvider .checkAppObjectForUpdate(app!)) && @@ -52,7 +52,7 @@ class _AppPageState extends State { ? 'Install' : 'Update'))), const SizedBox(width: 16.0), - OutlinedButton( + ElevatedButton( onPressed: app?.currentDownloadId != null ? null : () { @@ -85,7 +85,8 @@ class _AppPageState extends State { }); }, style: TextButton.styleFrom( - foregroundColor: Theme.of(context).errorColor), + foregroundColor: Theme.of(context).errorColor, + surfaceTintColor: Theme.of(context).errorColor), child: const Text('Remove'), ), ])), diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index 3796794..af6eee1 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -17,83 +17,39 @@ class _AppsPageState extends State { var appsProvider = context.watch(); appsProvider.getUpdates(); - return Scaffold( - appBar: AppBar( - title: const Text('Obtainium'), - ), - body: Center( - child: appsProvider.loadingApps - ? const CircularProgressIndicator() - : appsProvider.apps.isEmpty - ? Text( - 'No Apps', - style: Theme.of(context).textTheme.headline4, - ) - : RefreshIndicator( - onRefresh: appsProvider.getUpdates, - child: ListView( - children: appsProvider.apps.values - .map( - (e) => ListTile( - title: Text('${e.author}/${e.name}'), - subtitle: - Text(e.installedVersion ?? 'Not Installed'), - trailing: e.installedVersion != null && - e.installedVersion != e.latestVersion - ? const Text('Update Available') - : null, - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - AppPage(appId: e.id)), - ); - }, - ), - ) - .toList(), - ), + return Center( + child: appsProvider.loadingApps + ? const CircularProgressIndicator() + : appsProvider.apps.isEmpty + ? Text( + 'No Apps', + style: Theme.of(context).textTheme.headline4, + ) + : RefreshIndicator( + onRefresh: appsProvider.getUpdates, + child: ListView( + children: appsProvider.apps.values + .map( + (e) => ListTile( + title: Text('${e.author}/${e.name}'), + subtitle: + Text(e.installedVersion ?? 'Not Installed'), + trailing: e.installedVersion != null && + e.installedVersion != e.latestVersion + ? const Text('Update Available') + : null, + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AppPage(appId: e.id)), + ); + }, + ), + ) + .toList(), ), - ), - bottomSheet: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: - const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Expanded( - child: appsProvider.apps.values.toList().where((e) { - return (e.installedVersion != null && - e.installedVersion != e.latestVersion); - }).isNotEmpty - ? OutlinedButton( - onPressed: () { - appsProvider.installUpdates().catchError((e) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(e.toString())), - ); - }); - }, - child: const Text('Update All')) - : Container()), - const SizedBox(width: 16.0), - OutlinedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const AddAppPage()), - ); - }, - child: const Text('Add App'), - ), - ])), - ], - ), + ), ); } } diff --git a/lib/pages/home.dart b/lib/pages/home.dart new file mode 100644 index 0000000..ff78f00 --- /dev/null +++ b/lib/pages/home.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:obtainium/pages/add_app.dart'; +import 'package:obtainium/pages/apps.dart'; +import 'package:obtainium/pages/settings.dart'; + +class HomePage extends StatefulWidget { + const HomePage({super.key}); + + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State { + int selectedIndex = 1; + List pages = [ + const SettingsPage(), + const AppsPage(), + const AddAppPage() + ]; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Obtainium')), + body: pages.elementAt(selectedIndex), + bottomNavigationBar: NavigationBar( + destinations: const [ + NavigationDestination( + icon: Icon(Icons.settings), label: 'Settings'), + NavigationDestination(icon: Icon(Icons.apps), label: 'Apps'), + NavigationDestination(icon: Icon(Icons.add), label: 'Add App'), + ], + onDestinationSelected: (int index) { + setState(() { + selectedIndex = index; + }); + }, + selectedIndex: selectedIndex, + )); + } +} diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart new file mode 100644 index 0000000..065f50e --- /dev/null +++ b/lib/pages/settings.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; + +class SettingsPage extends StatefulWidget { + const SettingsPage({super.key}); + + @override + State createState() => _SettingsPageState(); +} + +class _SettingsPageState extends State { + @override + Widget build(BuildContext context) { + return Center( + child: Text( + 'No Configurable Settings Yet.', + style: Theme.of(context).textTheme.bodyLarge, + textAlign: TextAlign.center, + )); + } +} diff --git a/pubspec.lock b/pubspec.lock index b1c7dfb..5faf770 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,13 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + archive: + dependency: transitive + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "3.3.1" args: dependency: transitive description: @@ -29,6 +36,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + cli_util: + dependency: transitive + description: + name: cli_util + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.5" clock: dependency: transitive description: @@ -43,6 +64,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.16.0" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" cupertino_icons: dependency: "direct main" description: @@ -57,6 +85,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.7.7" + dynamic_color: + dependency: "direct main" + description: + name: dynamic_color + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.3" fake_async: dependency: transitive description: @@ -97,6 +132,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.0" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.0" flutter_lints: dependency: "direct dev" description: @@ -149,6 +191,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.0.1" + image: + dependency: transitive + description: + name: image + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.0" js: dependency: transitive description: @@ -156,6 +205,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.6.4" + json_annotation: + dependency: transitive + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "4.6.0" lints: dependency: transitive description: @@ -413,6 +469,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.1.0" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.1" sdks: dart: ">=2.19.0-79.0.dev <3.0.0" - flutter: ">=3.0.0" + flutter: ">=3.1.0-0.0.pre.1036" diff --git a/pubspec.yaml b/pubspec.yaml index db043d0..2a82ce5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,10 +45,13 @@ dependencies: toast: ^0.3.0 webview_flutter: ^3.0.4 workmanager: ^0.5.0 + dynamic_color: ^1.5.3 + dev_dependencies: flutter_test: sdk: flutter + flutter_launcher_icons: ^0.10.0 # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is @@ -57,6 +60,12 @@ dev_dependencies: # rules and activating additional ones. flutter_lints: ^2.0.0 +flutter_icons: + android: true + image_path: "assets/icon.png" + adaptive_icon_background: "#282828" + adaptive_icon_foreground: "assets/icon.png" + # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -98,3 +107,8 @@ flutter: # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages + + fonts: + - family: Metropolis + fonts: + - asset: assets/fonts/Metropolis-Regular.otf \ No newline at end of file