diff --git a/src/main.rs b/src/main.rs index b37a033..ffa3d1f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use std::fs::{create_dir_all, File, OpenOptions, read_to_string}; use std::io::prelude::*; -use std::path::Path; +use std::path::{Path, PathBuf}; use convert_case::{Case, Casing}; use serde::Serialize; @@ -17,8 +17,13 @@ const FEATURE_ROOT_COMPONENT: &str = include_str!("templates/impl/di/FeatureRoot const ROOT_MODULE: &str = include_str!("templates/impl/di/RootModule.handlebars"); const SUBCOMPONENTS_MODULE: &str = include_str!("templates/impl/di/SubcomponentsModule.handlebars"); +// impl/directions +const FEATURE_DIRECTIONS: &str = include_str!("templates/impl/directions/FeatureDirections.handlebars"); +const SUBFEATURE_DIRECTION_TEMPLATE: &str = include_str!("templates/impl/directions/SubfeatureDirectionTemplate.handlebars"); + // impl/entry const FEATURE_ENTRY_IMPL: &str = include_str!("templates/impl/entry/FeatureEntryImpl.handlebars"); +const SUBFEATURE_COMPOSABLE_TEMPLATE: &str = include_str!("templates/impl/entry/SubfeatureComposableTemplate.handlebars"); // impl/firstpage const PAGE_MODULE: &str = include_str!("templates/impl/firstpage/di/FirstPageModule.handlebars"); @@ -105,6 +110,77 @@ fn gen_screen(handlebars: &mut Handlebars, args: &Cli) { generate_page(&base_impl_package_path, handlebars, &context, page); add_subcomponent_to_component(page, module, &base_impl_package_path, args.base_package.as_ref().unwrap()); + amend_directions(handlebars, &context, module, &base_impl_package_path); + amend_feature_entry(handlebars, &context, module, &base_impl_package_path); +} + +fn amend_feature_entry(handlebars: &mut Handlebars, context: &BTreeMap, module: &String, base_impl_package_path: &PathBuf) { + let feature_entry_path = base_impl_package_path.join(format!("entry/{}FeatureEntryImpl.kt", module.to_case(Case::Pascal))); + let mut lines = read_to_string(&feature_entry_path).unwrap() + .lines() + .map(|l| l.to_string()) + .collect::>(); + + add_line_after_matching_predicate( + &mut lines, + &|l| l.starts_with(" navigation"), + handlebars.render_template(SUBFEATURE_COMPOSABLE_TEMPLATE, &context).unwrap().as_str(), + ); + + add_line_after_matching_predicate( + &mut lines, + &|l| l.ends_with("Screen") && l.starts_with("import"), + handlebars.render_template( + "import {{ base_package }}.{{ flat module }}.impl.{{ flat first_page }}.screen.{{ pascal first_page }}Screen", + &context).unwrap().as_str(), + ); + + let mut file = OpenOptions::new() + .write(true) + .truncate(true) + .open(&feature_entry_path) + .unwrap(); + + file.write(lines.join("\n").as_bytes()).unwrap(); +} + +fn amend_directions(handlebars: &mut Handlebars, context: &BTreeMap, module: &String, base_impl_package_path: &PathBuf) { + let directions_path = base_impl_package_path.join("directions").join(format!("{}Directions.kt", module.to_case(Case::Pascal))); + let mut lines = read_to_string(&directions_path).unwrap() + .lines() + .map(|l| l.to_string()) + .collect::>(); + + + if lines.iter().find(|l| l.ends_with("EmptyInput")).is_none() { + add_line_after_matching_predicate( + &mut lines, + &|l| l.ends_with("NavigationCommandProvider"), + format!("import {}.navigation.EmptyInput", context.get("base_package").unwrap()).as_str(), + ); + } + + if lines.iter().find(|l| l.eq_ignore_ascii_case("import androidx.navigation.NamedNavArgument")).is_none() { + add_line_after_matching_predicate( + &mut lines, + &|l| l.contains("androidx.navigation."), + "import androidx.navigation.NamedNavArgument", + ); + } + + add_line_after_matching_predicate( + &mut lines, + &|l| l.starts_with("object"), + handlebars.render_template(SUBFEATURE_DIRECTION_TEMPLATE, &context).unwrap().as_str(), + ); + + let mut file = OpenOptions::new() + .write(true) + .truncate(true) + .open(&directions_path) + .unwrap(); + + file.write(lines.join("\n").as_bytes()).unwrap(); } fn add_subcomponent_to_component(screen_name: &str, module: &str, base_impl_package_path: &Path, base_package: &str) { @@ -374,9 +450,19 @@ fn generate_impl_files( generate_impl_di(base_impl_package, handlebars, data, module); generate_impl_entry(base_impl_package, handlebars, data, module); + generate_directions(base_impl_package, handlebars, data, module); generate_page(base_impl_package, handlebars, data, first_page); } +fn generate_directions(base_impl_package: &Path, handlebars: &Handlebars, data: &T, module: &str) { + let directions_package = base_impl_package.join("directions"); + let directions_package = directions_package.as_path(); + create_dir_all(directions_package).unwrap(); + + let file_name = format!("{}Directions.kt", module.to_case(Case::Pascal)); + generate_file(&directions_package, handlebars, data, &file_name, FEATURE_DIRECTIONS); +} + fn generate_impl_di(base_impl_package: &Path, handlebars: &Handlebars, data: &T, module: &str) { let di_package = base_impl_package.join("di"); let di_package = di_package.as_path(); diff --git a/src/templates/impl/directions/FeatureDirections.handlebars b/src/templates/impl/directions/FeatureDirections.handlebars new file mode 100644 index 0000000..1907076 --- /dev/null +++ b/src/templates/impl/directions/FeatureDirections.handlebars @@ -0,0 +1,12 @@ +package {{ base_package }}.{{ flat module }}.impl.directions + +import androidx.navigation.NavType +import androidx.navigation.navArgument +import {{ base_package }}.{{ flat module }}.api.{{ pascal module }}FeatureEntry +import {{ base_package }}.navigation.EmptyInput +import {{ base_package }}.navigation.NavigationCommand +import {{ base_package }}.navigation.NavigationCommandProvider + +object {{ pascal module }}Directions { + object {{ pascal first_page }} : NavigationCommandProvider by {{ pascal module }}FeatureEntry +} \ No newline at end of file diff --git a/src/templates/impl/directions/SubfeatureDirectionTemplate.handlebars b/src/templates/impl/directions/SubfeatureDirectionTemplate.handlebars new file mode 100644 index 0000000..145a25c --- /dev/null +++ b/src/templates/impl/directions/SubfeatureDirectionTemplate.handlebars @@ -0,0 +1,10 @@ + object {{ pascal first_page }} : NavigationCommandProvider { + override val featureRoute: String = "{{ kebab first_page }}/" + override val arguments = emptyList() + + override fun destination(input: EmptyInput) = object : NavigationCommand { + override val arguments = {{ pascal first_page }}.arguments + override val destinationFeatureRoute = {{ pascal module }}FeatureEntry.featureRoute + override val destination: String = "{{ kebab first_page }}/" + } + } \ No newline at end of file diff --git a/src/templates/impl/entry/FeatureEntryImpl.handlebars b/src/templates/impl/entry/FeatureEntryImpl.handlebars index 1c18602..1d8586f 100644 --- a/src/templates/impl/entry/FeatureEntryImpl.handlebars +++ b/src/templates/impl/entry/FeatureEntryImpl.handlebars @@ -6,12 +6,12 @@ import androidx.navigation.NavBackStackEntry import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController -import androidx.navigation.compose.composable import androidx.navigation.navigation import {{ base_package }}.data.api.DataProvider import {{ base_package }}.{{ flat module }}.api.{{ pascal module }}FeatureEntry import {{ base_package }}.{{ flat module }}.impl.di.Dagger{{ pascal module }}RootComponent import {{ base_package }}.{{ flat module }}.impl.di.{{ pascal module }}RootComponent +import {{ base_package }}.{{ flat module }}.impl.directions.{{ pascal module }}Directions import {{ base_package }}.{{ flat module }}.impl.{{ flat first_page }}.screen.{{ pascal first_page }}Screen import {{ base_package }}.navigation.CompositionLocals import {{ base_package }}.navigation.RootComponentHolder @@ -19,6 +19,7 @@ import {{ base_package }}.navigation.injectedViewModel import {{ base_package }}.navigation.rememberScoped import {{ base_package }}.platform.PlatformProvider import {{ base_package }}.navigation.NavigationProvider +import {{ base_package }}.navigation.composable import javax.inject.Inject /** @@ -33,7 +34,7 @@ class {{ pascal module }}FeatureEntryImpl @Inject constructor() : {{ pascal modu navController: NavHostController, ) { navigation(startDestination = featureRoute, route = rootRoute) { - composable(featureRoute, arguments) { backstackEntry -> + composable({{ pascal module }}Directions.{{ pascal first_page }}) { backstackEntry -> val rootComponent = rootComponent(backstackEntry, navController) val viewModel = injectedViewModel(backstackEntry) { diff --git a/src/templates/impl/entry/SubfeatureComposableTemplate.handlebars b/src/templates/impl/entry/SubfeatureComposableTemplate.handlebars new file mode 100644 index 0000000..16a8713 --- /dev/null +++ b/src/templates/impl/entry/SubfeatureComposableTemplate.handlebars @@ -0,0 +1,9 @@ + composable({{ pascal module }}Directions.{{ pascal first_page }}) { backstackEntry -> + val rootComponent = rootComponent(backstackEntry, navController) + + val viewModel = injectedViewModel(backstackEntry) { + rootComponent.{{ camel first_page }}SubcomponentFactory.create().viewModel + } + + {{ pascal first_page }}Screen(viewModel = viewModel) + } \ No newline at end of file