diff --git a/README.md b/README.md index 4d38df8..1951ac3 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,16 @@ -# Prod +# prod -Project dashboard to launch editors, websites and terminals \ No newline at end of file +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..be07d4b --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options +plugins: + riverpod_lint: ^3.1.3 diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..63b546e --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import "package:prod/views/editors.dart"; +import "package:prod/views/home.dart"; +import "package:yaru/yaru.dart"; +import "package:provider/provider.dart"; +import "package:prod/models/globalModel.dart"; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(context) { + return ChangeNotifierProvider( + lazy: false, + create: (context) => GlobalModel(), + builder: (context, child) { + return YaruTheme( + builder: (context, yaru, child) { + return MaterialApp( + theme: yaru.theme, + themeMode: .system, + darkTheme: yaru.darkTheme, + routes: { + "/": (context) => HomePage(), + "/editors": (context) => EditorEditor(), + }, + initialRoute: "/", + ); + }, + ); + }, + ); + } +} diff --git a/lib/models/editor.dart b/lib/models/editor.dart new file mode 100644 index 0000000..4577181 --- /dev/null +++ b/lib/models/editor.dart @@ -0,0 +1,20 @@ +import "package:flutter/material.dart"; +import "package:process_run/which.dart"; + +class Editor { + final String name; + final String command; + final String commandTemplate; + // final Icon icon; + + const Editor(this.name, this.command, this.commandTemplate); + + bool validateCommand() { + final String? fullPath = whichSync(command); + if (fullPath == null) { + return false; + } else { + return true; + } + } +} diff --git a/lib/models/globalModel.dart b/lib/models/globalModel.dart new file mode 100644 index 0000000..3ae6401 --- /dev/null +++ b/lib/models/globalModel.dart @@ -0,0 +1,68 @@ +import 'package:flutter/foundation.dart'; +import 'package:prod/models/editor.dart'; +import 'package:prod/models/project.dart'; + +class GlobalModel extends ChangeNotifier { + late List projects; + late List hoverShow; + late List editors; + + GlobalModel() { + projects = []; + editors = []; + hoverShow = List.filled(projects.length, false, growable: true); + } + + void addPrj(Project prj) { + projects.add(prj); + hoverShow.add(false); + notifyListeners(); + } + + void delPrj(int index) { + projects.removeAt(index); + hoverShow.removeAt(index); + notifyListeners(); + } + + List get lsPrj { + return projects; + } + + int get lenPrj { + return projects.length; + } + + Project nthPrj(int index) { + return projects[index]; + } + + void setHoverShow(int index, bool state) { + hoverShow[index] = state; + notifyListeners(); + } + + bool getHoverShow(int index) { + return hoverShow[index]; + } + + // Editor List Management. + + void addEdt(Editor edt) { + editors.add(edt); + notifyListeners(); + } + + void delEdt(int index) { + editors.removeAt(index); + notifyListeners(); + } + + int get lenEdt { + return editors.length; + } + + Editor nthEdt(int index) { + return editors[index]; + } +} diff --git a/lib/models/project.dart b/lib/models/project.dart new file mode 100644 index 0000000..67c0ef0 --- /dev/null +++ b/lib/models/project.dart @@ -0,0 +1,44 @@ +import 'package:prod/models/editor.dart'; +import "dart:io"; +import "package:path/path.dart" as p; + +class Project { + final String name; + final String language; + final File path; + final List editors; + final bool isGit; + final bool enableTerminal; + + Project( + this.name, + this.language, + this.path, + this.editors, + this.isGit, + this.enableTerminal, + ); + + factory Project.validated( + String name, + String lang, + String path, + List editors, + bool enableTerminal, + ) { + final File fpath = File(path); + if (fpath.existsSync()) { + print("Project not found!!!"); + } else { + print("Project Exists on disk"); + } + String gitPath = p.join(path, ".git", "HEAD"); + final bool isGit = File(gitPath).existsSync(); + + return Project(name, lang, fpath, editors, isGit, enableTerminal); + } + + // bool validatePath(){ + // return File + // } +} diff --git a/lib/views/editors.dart b/lib/views/editors.dart new file mode 100644 index 0000000..37ef835 --- /dev/null +++ b/lib/views/editors.dart @@ -0,0 +1,38 @@ +import "package:flutter/material.dart"; +import "package:prod/models/editor.dart"; +import "package:prod/models/globalModel.dart"; +import "package:prod/widgets/editorCard.dart"; +import "package:provider/provider.dart"; + +class EditorEditor extends StatelessWidget { + const EditorEditor({super.key}); + + @override + Widget build(BuildContext context) { + GlobalModel gm = Provider.of(context); + return Scaffold( + appBar: AppBar(title: Text("PROject Dashboard")), + body: gm.lenEdt > 0 + ? LayoutBuilder( + builder: (context, constraints) { + int cols = (constraints.maxWidth / 200).floor(); + return GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: cols, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + ), + itemCount: gm.lenEdt, + itemBuilder: (context, index) => EditorCard(index), + ); + }, + ) + : Center( + child: Text( + "Add editors by pressing the \"+\" button.", + style: TextStyle(fontSize: 20), + ), + ), + ); + } +} diff --git a/lib/views/home.dart b/lib/views/home.dart new file mode 100644 index 0000000..ce4089f --- /dev/null +++ b/lib/views/home.dart @@ -0,0 +1,48 @@ +import "package:prod/models/globalModel.dart"; +import "package:prod/models/project.dart"; +import "package:prod/widgets/prjFAB.dart"; +import "package:prod/widgets/drawer.dart"; +import "package:prod/widgets/projectCard.dart"; +import "package:provider/provider.dart"; +import "package:yaru/yaru.dart"; +import "package:flutter/material.dart"; + +class HomePage extends StatelessWidget { + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + GlobalModel gm = Provider.of(context); + return SafeArea( + child: Scaffold( + appBar: AppBar(title: Text("PROject Dashboard")), + drawer: ProdDrawer(), + floatingActionButton: ProjFAB(), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: gm.lenPrj > 0 + ? LayoutBuilder( + builder: (context, constraints) { + int cols = (constraints.maxWidth / 200).floor(); + return GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: cols, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + ), + itemCount: gm.lenPrj, + itemBuilder: (context, index) => ProjectCard(index), + ); + }, + ) + : Center( + child: Text( + "Add projects by pressing the \"+\" button.", + style: TextStyle(fontSize: 20), + ), + ), + ), + ), + ); + } +} diff --git a/lib/widgets/drawer.dart b/lib/widgets/drawer.dart new file mode 100644 index 0000000..f9f871f --- /dev/null +++ b/lib/widgets/drawer.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:prod/widgets/simpleListTile.dart'; + +class ProdDrawer extends StatelessWidget { + const ProdDrawer({super.key}); + + @override + Widget build(BuildContext context) { + return Drawer( + child: ListView( + children: [ + ListTile( + leading: Icon(Icons.arrow_back), + title: Text("Back"), + onTap: () => Navigator.pop(context), + ), + Divider(), + SListTile(Icons.backup_table, "Editors", "/editors"), + SListTile(Icons.settings, "Settings", "/settings"), + ], + ), + ); + } +} diff --git a/lib/widgets/editorCard.dart b/lib/widgets/editorCard.dart new file mode 100644 index 0000000..2b6f0ab --- /dev/null +++ b/lib/widgets/editorCard.dart @@ -0,0 +1,31 @@ +import "package:flutter/material.dart"; +import "package:prod/models/editor.dart"; +import "package:prod/models/globalModel.dart"; +import "package:prod/models/project.dart"; +import "package:provider/provider.dart"; +import "package:yaru/yaru.dart"; +import "package:process_run/shell.dart"; + +class EditorCard extends StatelessWidget { + const EditorCard(this.id, {super.key}); + final int id; + + @override + Widget build(BuildContext context) { + GlobalModel gm = Provider.of(context); + final Editor edt = gm.nthEdt(id); + return YaruBanner( + onTap: () {}, + child: Center( + child: Column( + mainAxisAlignment: .start, + crossAxisAlignment: .start, + children: [ + Text("${edt.name}", style: TextStyle(fontSize: 30)), + Text("${edt.commandTemplate}"), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/edtFAB.dart b/lib/widgets/edtFAB.dart new file mode 100644 index 0000000..e4b2d00 --- /dev/null +++ b/lib/widgets/edtFAB.dart @@ -0,0 +1,24 @@ +import "package:flutter/material.dart"; +import "package:prod/models/editor.dart"; + +class EditorFAB extends StatelessWidget { + const EditorFAB({super.key}); + + @override + Widget build(BuildContext context) { + return FloatingActionButton( + onPressed: () { + // gm.add( + // Project.validated( + // "Kimi", + // "Rust", + // "/home/arrow/Gitted/cowin", + // [], + // true, + // ), + // ); + }, + child: Icon(Icons.add), + ); + } +} diff --git a/lib/widgets/prjFAB.dart b/lib/widgets/prjFAB.dart new file mode 100644 index 0000000..5679873 --- /dev/null +++ b/lib/widgets/prjFAB.dart @@ -0,0 +1,26 @@ +import "package:flutter/material.dart"; +import "package:prod/models/globalModel.dart"; +import "package:prod/models/project.dart"; +import "package:provider/provider.dart"; + +class ProjFAB extends StatelessWidget { + const ProjFAB({super.key}); + + @override + Widget build(BuildContext context) { + return FloatingActionButton( + onPressed: () { + Provider.of(context, listen: false).addPrj( + Project.validated( + "Kimi", + "Rust", + "/home/arrow/Gitted/cowin", + [], + true, + ), + ); + }, + child: Icon(Icons.add), + ); + } +} diff --git a/lib/widgets/projectCard.dart b/lib/widgets/projectCard.dart new file mode 100644 index 0000000..17feabd --- /dev/null +++ b/lib/widgets/projectCard.dart @@ -0,0 +1,58 @@ +import "package:flutter/material.dart"; +import "package:prod/models/globalModel.dart"; +import "package:prod/models/project.dart"; +import "package:provider/provider.dart"; +import "package:yaru/yaru.dart"; +import "package:process_run/shell.dart"; + +class ProjectCard extends StatelessWidget { + const ProjectCard(this.id, {super.key}); + final int id; + + @override + Widget build(BuildContext context) { + GlobalModel gm = Provider.of(context); + final Project prj = gm.nthPrj(id); + return YaruBanner( + padding: .only( + left: kYaruPagePadding, + top: kYaruPagePadding, + bottom: kYaruPagePadding, + right: kYaruPagePadding, + ), + onHover: (st) => gm.setHoverShow(id, st), + onTap: () { + var shell = Shell(); + shell.run("konsole -e nvim ${prj.path.path}"); + }, + child: Center( + child: Column( + mainAxisAlignment: .start, + crossAxisAlignment: .start, + children: [ + Row( + mainAxisAlignment: .spaceBetween, + children: [ + Text("${prj.name}", style: TextStyle(fontSize: 30)), + gm.getHoverShow(id) + ? ElevatedButton( + child: Icon(Icons.close), + onPressed: () => print("pressed delete"), + ) + : Container(), + ], + ), + Row( + spacing: 10, + children: [ + Text("${prj.language}"), + prj.isGit ? Icon(Icons.commit) : Container(), + ], + ), + gm.getHoverShow(id) ? Text("${prj.path.path}") : Container(), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/simpleListTile.dart b/lib/widgets/simpleListTile.dart new file mode 100644 index 0000000..930d630 --- /dev/null +++ b/lib/widgets/simpleListTile.dart @@ -0,0 +1,21 @@ +//ignore_for_file: file_names + +import "package:flutter/material.dart"; + +class SListTile extends StatelessWidget { + const SListTile(this.icon, this.title, this.route, {super.key}); + final IconData icon; + final String title; + final String route; + + @override + Widget build(BuildContext context) { + return ListTile( + leading: Icon(icon), + title: Text(title), + onTap: () { + Navigator.pushNamed(context, route); + }, + ); + } +} diff --git a/linux/.gitignore b/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt new file mode 100644 index 0000000..84421b7 --- /dev/null +++ b/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "prod") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "net.inaph.prod") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/linux/flutter/CMakeLists.txt b/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/linux/runner/CMakeLists.txt b/linux/runner/CMakeLists.txt new file mode 100644 index 0000000..e97dabc --- /dev/null +++ b/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/linux/runner/main.cc b/linux/runner/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/linux/runner/my_application.cc b/linux/runner/my_application.cc new file mode 100644 index 0000000..c40d017 --- /dev/null +++ b/linux/runner/my_application.cc @@ -0,0 +1,148 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Called when first Flutter frame received. +static void first_frame_cb(MyApplication* self, FlView* view) { + gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view))); +} + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "prod"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "prod"); + } + + gtk_window_set_default_size(window, 1280, 720); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments( + project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + GdkRGBA background_color; + // Background defaults to black, override it here if necessary, e.g. #00000000 + // for transparent. + gdk_rgba_parse(&background_color, "#000000"); + fl_view_set_background_color(view, &background_color); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + // Show the window when Flutter renders. + // Requires the view to be realized so we can start rendering. + g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), + self); + gtk_widget_realize(GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, + gchar*** arguments, + int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = + my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, "flags", + G_APPLICATION_NON_UNIQUE, nullptr)); +} diff --git a/linux/runner/my_application.h b/linux/runner/my_application.h new file mode 100644 index 0000000..db16367 --- /dev/null +++ b/linux/runner/my_application.h @@ -0,0 +1,21 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, + my_application, + MY, + APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/prod.iml b/prod.iml new file mode 100644 index 0000000..f66303d --- /dev/null +++ b/prod.iml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..01e4c16 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,91 @@ +name: prod +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: "none" # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ^3.10.8 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + process_run: ^1.3.0 + yaru: ^9.0.1 + path: ^1.9.1 + provider: ^6.1.5+1 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..0294f9b --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:prod/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +}