@@ -36,12 +36,13 @@ public class MainActivity extends AppCompatActivity {
3636
3737 private static final int PERMISSION_REQUEST_CODE = 100 ;
3838
39- private MaterialCardView cardExtract , cardCreate ;
40- private TextView tvStatus , tvExtractStatus , tvCreateStatus ;
39+ private MaterialCardView cardExtract , cardCreate , cardDecompile ;
40+ private TextView tvStatus , tvExtractStatus , tvCreateStatus , tvDecompileStatus ;
4141 private ProgressBar progressBar ;
4242
4343 private Python python ;
4444 private PyObject rpaModule ;
45+ private PyObject decompileModule ;
4546
4647 private ExecutorService executorService ;
4748
@@ -50,6 +51,7 @@ public class MainActivity extends AppCompatActivity {
5051 private ActivityResultLauncher <Intent > extractDirPickerLauncher ;
5152 private ActivityResultLauncher <Intent > createSourcePickerLauncher ;
5253 private ActivityResultLauncher <Intent > createOutputPickerLauncher ;
54+ private ActivityResultLauncher <Intent > decompileDirPickerLauncher ;
5355
5456 // Temporary storage for multi-step file picking
5557 private String selectedRpaPath ;
@@ -68,6 +70,7 @@ protected void onCreate(Bundle savedInstanceState) {
6870 }
6971 python = Python .getInstance ();
7072 rpaModule = python .getModule ("rpa_wrapper" );
73+ decompileModule = python .getModule ("decompile_wrapper" );
7174
7275 // Initialize executor service
7376 executorService = Executors .newSingleThreadExecutor ();
@@ -88,14 +91,17 @@ protected void onCreate(Bundle savedInstanceState) {
8891 private void initViews () {
8992 cardExtract = findViewById (R .id .card_extract );
9093 cardCreate = findViewById (R .id .card_create );
94+ cardDecompile = findViewById (R .id .card_decompile );
9195 tvStatus = findViewById (R .id .tv_status );
9296 tvExtractStatus = findViewById (R .id .tv_extract_status );
9397 tvCreateStatus = findViewById (R .id .tv_create_status );
98+ tvDecompileStatus = findViewById (R .id .tv_decompile_status );
9499 progressBar = findViewById (R .id .progress_bar );
95100
96101 // Set up click listeners
97102 cardExtract .setOnClickListener (v -> startExtractFlow ());
98103 cardCreate .setOnClickListener (v -> startCreateFlow ());
104+ cardDecompile .setOnClickListener (v -> startDecompileFlow ());
99105 }
100106
101107
@@ -212,6 +218,31 @@ private void initFilePickerLaunchers() {
212218 showOutputFileNameDialog ();
213219 }
214220 });
221+
222+ // Decompile: Pick source directory
223+ decompileDirPickerLauncher = registerForActivityResult (
224+ new ActivityResultContracts .StartActivityForResult (),
225+ result -> {
226+ if (result .getResultCode () == Activity .RESULT_OK && result .getData () != null ) {
227+ String sourcePath = null ;
228+
229+ // Check for multi-select first (user may have accidentally entered multi-select mode)
230+ ArrayList <String > selectedPaths = result .getData ().getStringArrayListExtra (FilePickerActivity .EXTRA_SELECTED_PATHS );
231+ if (selectedPaths != null && !selectedPaths .isEmpty ()) {
232+ // If multi-select happened, just use the first path
233+ sourcePath = selectedPaths .get (0 );
234+ } else {
235+ // Normal single selection
236+ sourcePath = result .getData ().getStringExtra (FilePickerActivity .EXTRA_SELECTED_PATH );
237+ }
238+
239+ if (sourcePath != null && !sourcePath .isEmpty ()) {
240+ performDecompile (sourcePath );
241+ } else {
242+ Toast .makeText (this , "No directory selected" , Toast .LENGTH_SHORT ).show ();
243+ }
244+ }
245+ });
215246 }
216247
217248 private void startExtractFlow () {
@@ -705,18 +736,114 @@ private void performCreation(String sourceDirPath, String outputFilePath) {
705736 });
706737 }
707738
739+ private void startDecompileFlow () {
740+ // Launch file picker for directory containing .rpyc files
741+ Intent intent = new Intent (this , FilePickerActivity .class );
742+ intent .putExtra (FilePickerActivity .EXTRA_MODE , FilePickerActivity .MODE_DIRECTORY );
743+ intent .putExtra (FilePickerActivity .EXTRA_TITLE , "Select Folder with RPYC Files" );
744+ decompileDirPickerLauncher .launch (intent );
745+ }
746+
747+ private void performDecompile (String sourceDirPath ) {
748+ // Clear any old progress data before starting
749+ ProgressTracker tracker = new ProgressTracker (MainActivity .this );
750+ tracker .clearProgress ();
751+
752+ // Launch progress activity
753+ Intent intent = new Intent (this , ProgressActivity .class );
754+ startActivity (intent );
755+
756+ executorService .execute (() -> {
757+ try {
758+ // Initialize progress with start time
759+ ProgressData initialData = new ProgressData ();
760+ initialData .operation = "decompile" ;
761+ initialData .status = "in_progress" ;
762+ initialData .startTime = System .currentTimeMillis ();
763+ initialData .lastUpdateTime = System .currentTimeMillis ();
764+ initialData .totalFiles = 0 ;
765+ initialData .processedFiles = 0 ;
766+ initialData .currentFile = "Starting decompilation..." ;
767+ tracker .writeProgress (initialData );
768+
769+ // Get progress file path
770+ String progressFilePath = tracker .getProgressFilePath ();
771+
772+ // Call Python decompilation
773+ PyObject result = decompileModule .callAttr ("decompile_directory" ,
774+ sourceDirPath ,
775+ progressFilePath );
776+
777+ // Check if result is valid
778+ if (result == null ) {
779+ throw new Exception ("Python function returned null" );
780+ }
781+
782+ // Access dictionary items using __getitem__
783+ PyObject successObj = result .callAttr ("__getitem__" , "success" );
784+ PyObject messageObj = result .callAttr ("__getitem__" , "message" );
785+ PyObject statsObj = result .callAttr ("__getitem__" , "stats" );
786+
787+ final boolean success = successObj .toJava (Boolean .class );
788+ final String message = messageObj .toJava (String .class );
789+
790+ // Extract stats
791+ PyObject totalObj = statsObj .callAttr ("__getitem__" , "total" );
792+ PyObject successCountObj = statsObj .callAttr ("__getitem__" , "success" );
793+ PyObject skippedObj = statsObj .callAttr ("__getitem__" , "skipped" );
794+ PyObject failedObj = statsObj .callAttr ("__getitem__" , "failed" );
795+
796+ final int total = totalObj .toJava (Integer .class );
797+ final int successCount = successCountObj .toJava (Integer .class );
798+ final int skipped = skippedObj .toJava (Integer .class );
799+ final int failed = failedObj .toJava (Integer .class );
800+
801+ runOnUiThread (() -> {
802+ if (success ) {
803+ tvDecompileStatus .setText (String .format (
804+ "Decompiled %d files (%d success, %d skipped, %d failed)" ,
805+ total , successCount , skipped , failed
806+ ));
807+ } else {
808+ tvDecompileStatus .setText ("Decompilation failed: " + message );
809+ }
810+ });
811+
812+ } catch (Exception e ) {
813+ e .printStackTrace ();
814+
815+ // Update progress file with error
816+ try {
817+ ProgressData errorData = new ProgressData ();
818+ errorData .operation = "decompile" ;
819+ errorData .status = "failed" ;
820+ errorData .errorMessage = "Error: " + e .getMessage ();
821+ tracker .writeProgress (errorData );
822+ } catch (Exception ex ) {
823+ ex .printStackTrace ();
824+ }
825+
826+ runOnUiThread (() -> {
827+ tvDecompileStatus .setText ("Decompilation error: " + e .getMessage ());
828+ });
829+ }
830+ });
831+ }
832+
708833 private void showProgress (String message ) {
709834 progressBar .setVisibility (View .VISIBLE );
710835 tvStatus .setText (message );
711836 cardExtract .setEnabled (false );
712837 cardCreate .setEnabled (false );
838+ cardDecompile .setEnabled (false );
713839 }
714840
715841 private void hideProgress () {
716842 progressBar .setVisibility (View .GONE );
717843 tvStatus .setText ("Ready" );
718844 cardExtract .setEnabled (true );
719845 cardCreate .setEnabled (true );
846+ cardDecompile .setEnabled (true );
720847 }
721848
722849 private void showSuccess (String message ) {
0 commit comments