import ' dart:developer ' ;
import ' package:flutter/foundation.dart ' ;
import ' package:flutter/material.dart ' ;
import ' package:dropdown_button2/dropdown_button2.dart ' ;
import ' package:flutter_gen/gen_l10n/l10n.dart ' ;
import ' package:matrix/matrix.dart ' ;
import ' package:fluffychat/pangea/activity_planner/activity_mode_list_repo.dart ' ;
import ' package:fluffychat/pangea/activity_planner/activity_plan_generation_repo.dart ' ;
import ' package:fluffychat/pangea/activity_planner/activity_plan_tile.dart ' ;
import ' package:fluffychat/pangea/activity_planner/learning_objective_list_repo.dart ' ;
import ' package:fluffychat/pangea/activity_planner/list_request_schema.dart ' ;
import ' package:fluffychat/pangea/activity_planner/media_enum.dart ' ;
import ' package:fluffychat/pangea/activity_planner/topic_list_repo.dart ' ;
import ' package:fluffychat/pangea/chat_settings/widgets/language_level_dropdown.dart ' ;
import ' package:fluffychat/pangea/common/constants/model_keys.dart ' ;
import ' package:fluffychat/pangea/events/models/pangea_token_model.dart ' ;
import ' package:fluffychat/pangea/events/models/representation_content_model.dart ' ;
import ' package:fluffychat/pangea/events/models/tokens_event_content_model.dart ' ;
import ' package:fluffychat/pangea/events/repo/token_api_models.dart ' ;
import ' package:fluffychat/pangea/extensions/pangea_room_extension.dart ' ;
import ' package:fluffychat/pangea/instructions/instructions_enum.dart ' ;
import ' package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart ' ;
import ' package:fluffychat/pangea/learning_settings/constants/language_constants.dart ' ;
import ' package:fluffychat/pangea/learning_settings/widgets/p_language_dropdown.dart ' ;
import ' package:fluffychat/widgets/future_loading_dialog.dart ' ;
import ' package:fluffychat/widgets/matrix.dart ' ;
// a page to allow the user to choose settings and then generate a list of activities
// has app bar with a back button to go back to content 1 (disabled if on content 1), and a title of "Activity Planner", and close button to close the activity planner
// content 1 - settings
// content 2 - display of activities generated by the system, allowing edit and selection
// use standard flutter material widgets and theme colors/styles. all copy should be defined in intl_en.arb and used with L10n.of(context).copyKey
// content 1
// should have a maxWidth, pulled from appconfig
// a. topic input with drop-down of suggestions pulled from TopicListRepo. label text of "topic" and placeholder of some random suggestions from repo
// b. mode input with drop-down of suggestions pulled from ModeListRepo. label text of "mode" and placeholder of some random suggestions from repo
// c. objective input with drop-down of suggestions pulled from LearningObjectiveListRepo. label text of "learning objective" and placeholder of some random suggestions from repo.
// e. dropdown for media type with text "media students should share as part of the activity"
// d. dropdown for selecting "language of activity instructions" which is auto-populated with the user's l1 but can be changed with options coming from pangeaController.pLanguageStore.baseOptions
// f. dropdown for selecting "target language" which is auto-populated with the user's l2 but can be changed with options coming from pangeaController.pLanguageStore.targetOptions
// g. selection for language level
// h. button to generate activities
// content 2
// a. app bar with a back button to go back to content 1, and a title of "Activity Planner", and close button to close the activity planner
// b. display of activities generated by the system, arranged in a column. if there is enough horizontal space, the activities should be arranged in a row
// a1. each activity should have a button to "launch activity" which calls a callback. this can be blank for now.
// a2. each activity should have a button to edit the activity. upon edit, the activity should become an input form where the user can freely edit the activity content
enum _PageMode {
settings ,
activities ,
}
class ActivityPlannerPage extends StatefulWidget {
final String roomID ;
const ActivityPlannerPage ( { super . key , required this . roomID } ) ;
@ override
ActivityPlannerPageState createState ( ) = > ActivityPlannerPageState ( ) ;
}
class ActivityPlannerPageState extends State < ActivityPlannerPage > {
final _formKey = GlobalKey < FormState > ( ) ;
/// Index of the content to display
_PageMode _pageMode = _PageMode . settings ;
/// Selected values from the form
String ? _selectedTopic ;
String ? _selectedMode ;
String ? _selectedObjective ;
MediaEnum _selectedMedia = MediaEnum . nan ;
String ? _selectedLanguageOfInstructions ;
String ? _selectedTargetLanguage ;
int ? _selectedCefrLevel ;
/// fetch data from repos
List < ActivitySettingResponseSchema > _topicItems = [ ] ;
List < ActivitySettingResponseSchema > _modeItems = [ ] ;
List < ActivitySettingResponseSchema > _objectiveItems = [ ] ;
/// List of activities generated by the system
List < String > _activities = [ ] ;
final _topicSearchController = TextEditingController ( ) ;
final _objectiveSearchController = TextEditingController ( ) ;
final List < TextEditingController > _activityControllers = [ ] ;
Room ? get room = > Matrix . of ( context ) . client . getRoomById ( widget . roomID ) ;
@ override
void initState ( ) {
super . initState ( ) ;
if ( room = = null ) {
Navigator . of ( context ) . pop ( ) ;
return ;
}
_loadDropdownData ( ) ;
_selectedLanguageOfInstructions =
MatrixState . pangeaController . languageController . userL1 ? . langCode ;
_selectedTargetLanguage =
MatrixState . pangeaController . languageController . userL2 ? . langCode ;
_selectedCefrLevel = 0 ;
// Initialize controllers for activity editing
for ( final activity in _activities ) {
_activityControllers . add ( TextEditingController ( text: activity ) ) ;
}
}
@ override
void dispose ( ) {
_topicSearchController . dispose ( ) ;
_objectiveSearchController . dispose ( ) ;
disposeAndClearActivityControllers ( ) ;
super . dispose ( ) ;
}
void disposeAndClearActivityControllers ( ) {
for ( final controller in _activityControllers ) {
controller . dispose ( ) ;
}
_activityControllers . clear ( ) ;
}
ActivitySettingRequestSchema get req = > ActivitySettingRequestSchema (
langCode:
MatrixState . pangeaController . languageController . userL2 ? . langCode ? ?
LanguageKeys . defaultLanguage ,
) ;
Future < void > _loadDropdownData ( ) async {
final topics = await TopicListRepo . get ( req ) ;
final modes = await ActivityModeListRepo . get ( req ) ;
final objectives = await LearningObjectiveListRepo . get ( req ) ;
setState ( ( ) {
_topicItems = topics ;
_modeItems = modes ;
_objectiveItems = objectives ;
} ) ;
}
// send the activity as a message to the room
Future < void > onLaunch ( int index ) = > showFutureLoadingDialog (
context: context ,
future: ( ) async {
// this shouldn't often error but just in case since it's not necessary for the activity to be sent
List < PangeaToken > ? tokens ;
try {
tokens = await MatrixState . pangeaController . messageData . getTokens (
repEventId: null ,
req: TokensRequestModel (
fullText: _activities [ index ] ,
langCode: _selectedLanguageOfInstructions ! ,
senderL1: _selectedLanguageOfInstructions ! ,
senderL2: _selectedLanguageOfInstructions ! ,
) ,
room: null ,
) ;
} catch ( e ) {
debugger ( when: kDebugMode ) ;
}
final eventId = await room ? . pangeaSendTextEvent (
_activities [ index ] ,
messageTag: ModelKey . messageTagActivityPlan ,
originalSent: PangeaRepresentation (
langCode: _selectedLanguageOfInstructions ! ,
text: _activities [ index ] ,
originalSent: true ,
originalWritten: false ,
) ,
tokensSent:
tokens ! = null ? PangeaMessageTokens ( tokens: tokens ) : null ,
) ;
if ( eventId = = null ) {
debugger ( when: kDebugMode ) ;
return ;
}
await room ? . setPinnedEvents ( [ eventId ] ) ;
Navigator . of ( context ) . pop ( ) ;
} ,
) ;
Future < void > _generateActivities ( ) async {
if ( _formKey . currentState ? . validate ( ) ? ? false ) {
final request = ActivityPlanRequest (
topic: _selectedTopic ! ,
mode: _selectedMode ! ,
objective: _selectedObjective ! ,
media: _selectedMedia ,
languageOfInstructions: _selectedLanguageOfInstructions ! ,
targetLanguage: _selectedTargetLanguage ! ,
cefrLevel: _selectedCefrLevel ! ,
) ;
await showFutureLoadingDialog (
context: context ,
future: ( ) async {
final response = await ActivityPlanGenerationRepo . get ( request ) ;
setState ( ( ) {
_activities = response . activityPlans ;
disposeAndClearActivityControllers ( ) ;
for ( final activity in _activities ) {
_activityControllers . add ( TextEditingController ( text: activity ) ) ;
}
_pageMode = _PageMode . activities ;
} ) ;
} ,
) ;
}
}
bool get _canRandomizeSelections = >
_topicItems . isNotEmpty & &
_objectiveItems . isNotEmpty & &
_modeItems . isNotEmpty ;
void _randomizeSelections ( ) {
if ( ! _canRandomizeSelections ) return ;
setState ( ( ) {
_selectedTopic = ( _topicItems . . shuffle ( ) ) . first . name ;
_selectedObjective = ( _objectiveItems . . shuffle ( ) ) . first . name ;
_selectedMode = ( _modeItems . . shuffle ( ) ) . first . name ;
} ) ;
}
// Add validation logic
String ? _validateNotNull ( String ? value ) {
if ( value = = null | | value . isEmpty ) {
return L10n . of ( context ) . interactiveTranslatorRequired ;
}
return null ;
}
@ override
Widget build ( BuildContext context ) {
final l10n = L10n . of ( context ) ;
return Scaffold (
appBar: AppBar (
leading: _pageMode = = _PageMode . settings
? IconButton (
icon: const Icon ( Icons . close ) ,
onPressed: ( ) = > Navigator . of ( context ) . pop ( ) ,
)
: IconButton (
onPressed: ( ) = > setState ( ( ) = > _pageMode = _PageMode . settings ) ,
icon: const Icon ( Icons . arrow_back ) ,
) ,
title: Text ( l10n . activityPlannerTitle ) ,
) ,
body: _pageMode = = _PageMode . settings
? _buildSettingsForm ( l10n )
: _buildActivitiesView ( l10n ) ,
) ;
}
Widget _buildSettingsForm ( L10n l10n ) {
return Center (
child: ConstrainedBox (
constraints: const BoxConstraints ( maxWidth: 600 ) ,
child: Form (
key: _formKey ,
child: ListView (
padding: const EdgeInsets . all ( 16 ) ,
children: [
const InstructionsInlineTooltip (
instructionsEnum: InstructionsEnum . activityPlannerOverview ,
) ,
DropdownButtonFormField2 < String > (
hint: Text ( l10n . topicPlaceholder ) ,
value: _selectedTopic ,
decoration: _selectedTopic ! = null
? InputDecoration (
labelText: l10n . topicLabel ,
)
: null ,
isExpanded: true ,
validator: ( value ) = > _validateNotNull ( value ) ,
dropdownSearchData: DropdownSearchData (
searchController: _topicSearchController ,
searchInnerWidget: InnerSearchWidget (
searchController: _topicSearchController ,
) ,
searchInnerWidgetHeight: 60 ,
searchMatchFn: ( item , searchValue ) {
return item . value
. toString ( )
. toLowerCase ( )
. contains ( searchValue . toLowerCase ( ) ) ;
} ,
) ,
items: _topicItems
. map (
( e ) = > DropdownMenuItem (
value: e . name ,
child: Text ( e . name ) ,
) ,
)
. toList ( ) ,
onChanged: ( value ) {
_selectedTopic = value ;
} ,
dropdownStyleData: const DropdownStyleData ( maxHeight: 400 ) ,
) ,
const SizedBox ( height: 24 ) ,
DropdownButtonFormField2 < String > (
hint: Text ( l10n . learningObjectivePlaceholder ) ,
decoration: _selectedObjective ! = null
? InputDecoration ( labelText: l10n . learningObjectiveLabel )
: null ,
validator: ( value ) = > _validateNotNull ( value ) ,
items: _objectiveItems
. map (
( e ) = >
DropdownMenuItem ( value: e . name , child: Text ( e . name ) ) ,
)
. toList ( ) ,
onChanged: ( val ) = > _selectedObjective = val ,
dropdownStyleData: const DropdownStyleData ( maxHeight: 400 ) ,
value: _selectedObjective ,
dropdownSearchData: DropdownSearchData (
searchController: _objectiveSearchController ,
searchInnerWidget: InnerSearchWidget (
searchController: _objectiveSearchController ,
) ,
searchInnerWidgetHeight: 60 ,
searchMatchFn: ( item , searchValue ) {
return item . value
. toString ( )
. toLowerCase ( )
. contains ( searchValue . toLowerCase ( ) ) ;
} ,
) ,
) ,
const SizedBox ( height: 24 ) ,
DropdownButtonFormField2 < String > (
decoration: _selectedMode ! = null
? InputDecoration ( labelText: l10n . modeLabel )
: null ,
hint: Text ( l10n . modePlaceholder ) ,
validator: ( value ) = > _validateNotNull ( value ) ,
items: _modeItems
. map (
( e ) = >
DropdownMenuItem ( value: e . name , child: Text ( e . name ) ) ,
)
. toList ( ) ,
onChanged: ( val ) = > _selectedMode = val ,
value: _selectedMode ,
) ,
const SizedBox ( height: 24 ) ,
DropdownButtonFormField2 < MediaEnum > (
decoration: InputDecoration ( labelText: l10n . mediaLabel ) ,
items: MediaEnum . values
. map (
( e ) = > DropdownMenuItem (
value: e ,
child: Text ( e . toDisplayCopyUsingL10n ( context ) ) ,
) ,
)
. toList ( ) ,
onChanged: ( val ) = > _selectedMedia = val ? ? MediaEnum . nan ,
value: _selectedMedia ,
) ,
const SizedBox ( height: 24 ) ,
LanguageLevelDropdown (
initialLevel: 0 ,
onChanged: ( val ) = > _selectedCefrLevel = val ,
) ,
const SizedBox ( height: 24 ) ,
PLanguageDropdown (
languages:
MatrixState . pangeaController . pLanguageStore . baseOptions ,
onChange: ( val ) = > _selectedTargetLanguage = val . langCode ,
initialLanguage:
MatrixState . pangeaController . languageController . userL1 ,
isL2List: false ,
decorationText: L10n . of ( context ) . languageOfInstructionsLabel ,
) ,
const SizedBox ( height: 24 ) ,
PLanguageDropdown (
languages:
MatrixState . pangeaController . pLanguageStore . targetOptions ,
onChange: ( val ) = > _selectedTargetLanguage = val . langCode ,
initialLanguage:
MatrixState . pangeaController . languageController . userL2 ,
decorationText: L10n . of ( context ) . targetLanguageLabel ,
isL2List: true ,
) ,
const SizedBox ( height: 24 ) ,
Row (
children: [
IconButton (
icon: const Icon ( Icons . shuffle ) ,
onPressed:
_canRandomizeSelections ? _randomizeSelections : null ,
) ,
const SizedBox ( width: 16 ) ,
Expanded (
child: ElevatedButton (
onPressed: _generateActivities ,
child: Text ( l10n . generateActivitiesButton ) ,
) ,
) ,
] ,
) ,
] ,
) ,
) ,
) ,
) ;
}
Widget _buildActivitiesView ( L10n l10n ) {
return ListView (
padding: const EdgeInsets . all ( 16 ) ,
children: _activities . asMap ( ) . entries . map ( ( entry ) {
final index = entry . key ;
return ActivityPlanTile (
activity: _activities [ index ] ,
onLaunch: ( ) = > onLaunch ( index ) ,
onEdit: ( val ) {
setState ( ( ) {
_activities [ index ] = val ;
} ) ;
} ,
) ;
} ) . toList ( ) ,
) ;
}
}
class InnerSearchWidget extends StatelessWidget {
const InnerSearchWidget ( {
super . key ,
required TextEditingController searchController ,
} ) : _objectiveSearchController = searchController ;
final TextEditingController _objectiveSearchController ;
@ override
Widget build ( BuildContext context ) {
return Padding (
padding: const EdgeInsets . only (
top: 8 ,
bottom: 4 ,
right: 8 ,
left: 8 ,
) ,
child: TextFormField (
controller: _objectiveSearchController ,
textInputAction: TextInputAction . search ,
decoration: InputDecoration (
isDense: true ,
contentPadding: const EdgeInsets . symmetric (
horizontal: 10 ,
vertical: 8 ,
) ,
hintText: L10n . of ( context ) . search ,
icon: const Icon ( Icons . search ) ,
border: OutlineInputBorder (
borderRadius: BorderRadius . circular ( 8 ) ,
) ,
) ,
) ,
) ;
}
}