@@ -85,8 +85,13 @@ public class DeployFragment extends Fragment {
8585 private View ledInternet , ledDevMode , ledDcpr , ledPpk ;
8686 private TextView txtDcpr , txtPpk , btnRefreshModules ;
8787 private LinearLayout rolesContainer , discrepancyWarning ;
88- private Button btnLaunchInstall , btnFastInstall , btnFastDelete , btnAdvancedReset ;
89- private Button btnAdvancedBackup , btnAdvancedRestore , btnAdvancedForceStop ;
88+ private Button btnLaunchInstall , btnAdvancedReset ;
89+ private ProgressButton btnFastInstall , btnFastDelete ;
90+ private Button btnAdvancedForceStop ;
91+ private ProgressButton btnAdvancedBackup , btnAdvancedRestore ;
92+ private LinearLayout restoreLogPanel ;
93+ private TextView restoreLogText , restoreLogResult ;
94+ private androidx .core .widget .NestedScrollView restoreLogScroll ;
9095
9196 // Backup Menu UI
9297 private TextView txtSelectBackupTitle , txtBackupStatus ;
@@ -137,6 +142,9 @@ public class DeployFragment extends Fragment {
137142 private boolean isRestoring = false ;
138143 private boolean isDeleting = false ;
139144 private boolean isImporting = false ;
145+ private static final String [] IMPORT_SPINNER = {"\u28BF " , "\u28FB " , "\u28FD " , "\u28FE " , "\u28F7 " , "\u28EF " , "\u28DF " , "\u287F " };
146+ private android .os .Handler importSpinnerHandler ;
147+ private int importSpinnerFrame = 0 ;
140148 private PRootEngine prootEngine ;
141149
142150 // Background Handlers
@@ -281,6 +289,14 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
281289 btnAdvancedBackup = view .findViewById (R .id .btn_advanced_backup );
282290 btnAdvancedRestore = view .findViewById (R .id .btn_advanced_restore );
283291 btnAdvancedForceStop = view .findViewById (R .id .btn_advanced_force_stop );
292+ restoreLogPanel = view .findViewById (R .id .restore_log_panel );
293+ restoreLogText = view .findViewById (R .id .restore_log_text );
294+ restoreLogResult = view .findViewById (R .id .restore_log_result );
295+ restoreLogScroll = view .findViewById (R .id .restore_log_scroll );
296+ View restoreLogClose = view .findViewById (R .id .restore_log_close );
297+ if (restoreLogClose != null ) {
298+ restoreLogClose .setOnClickListener (vv -> { if (restoreLogPanel != null ) restoreLogPanel .setVisibility (View .GONE ); });
299+ }
284300 txtSelectBackupTitle = view .findViewById (R .id .txt_select_backup_title );
285301 containerBackupList = view .findViewById (R .id .container_backup_list );
286302 txtBackupStatus = view .findViewById (R .id .txt_backup_status );
@@ -1044,6 +1060,7 @@ private void bindInstallButtonLogic(MainActivity mainAct, File debianRootfs, Fil
10441060 mainAct .invalidateModuleStateTrust ();
10451061 isDownloadingRootfs = true ;
10461062 btnFastInstall .setAlpha (0.8f );
1063+ btnFastInstall .startProgress ();
10471064 btnFastInstall .setTextSize (12f );
10481065
10491066 if (aria2Manager == null ) aria2Manager = new Aria2Manager ();
@@ -1183,6 +1200,7 @@ private void bindDeleteButtonLogic(MainActivity mainAct, File debianRootfs) {
11831200
11841201 mainAct .invalidateModuleStateTrust ();
11851202 btnFastDelete .setEnabled (false );
1203+ btnFastDelete .startProgress ();
11861204 Snackbar .make (getView (), R .string .install_status_deleting , Snackbar .LENGTH_SHORT ).show ();
11871205 new Thread (() -> {
11881206 enableSystemProtection ();
@@ -1195,7 +1213,7 @@ private void bindDeleteButtonLogic(MainActivity mainAct, File debianRootfs) {
11951213 mainAct .runOnUiThread (() -> Snackbar .make (getView (), getString (R .string .install_error_delete , e .getMessage ()), Snackbar .LENGTH_LONG ).show ());
11961214 } finally {
11971215 isDeleting = false ;
1198- mainAct .runOnUiThread (this :: updateDynamicButtons );
1216+ mainAct .runOnUiThread (() -> { btnFastDelete . stopProgress (); updateDynamicButtons (); } );
11991217 disableSystemProtection ();
12001218 }
12011219 }).start ();
@@ -1590,6 +1608,7 @@ private void finishInstallationSuccess() {
15901608 isDownloadingRootfs = false ;
15911609 if (isAdded () && getActivity () != null ) {
15921610 getActivity ().runOnUiThread (() -> {
1611+ btnFastInstall .stopProgress ();
15931612 btnFastInstall .setText (R .string .install_btn_reinstall );
15941613 btnFastInstall .setAlpha (1.0f );
15951614 updateDynamicButtons ();
@@ -1605,6 +1624,7 @@ private void abortInstallation(String message) {
16051624 isDownloadingRootfs = false ;
16061625 if (isAdded () && getActivity () != null ) {
16071626 getActivity ().runOnUiThread (() -> {
1627+ btnFastInstall .stopProgress ();
16081628 btnFastInstall .setText (R .string .install_btn_install );
16091629 btnFastInstall .setAlpha (1.0f );
16101630 updateDynamicButtons ();
@@ -1838,7 +1858,7 @@ private void bindBackupButtonLogic(MainActivity mainAct, File backupsDir, File i
18381858 .setMessage (getString (R .string .install_msg_backup_in_progress_body ))
18391859 .setPositiveButton (getString (R .string .install_btn_force_stop_process ), (dialog , which ) -> {
18401860 isBackupInProgress = false ;
1841- btnAdvancedBackup .setText (getString (R .string .install_btn_backup ));
1861+ btnAdvancedBackup .setText (getString (R .string .install_btn_backup )); btnAdvancedBackup . stopProgress ();
18421862 Snackbar .make (getView (), getString (R .string .install_msg_backup_aborted ), Snackbar .LENGTH_SHORT ).show ();
18431863 })
18441864 .setNegativeButton (getString (R .string .install_btn_let_finish ), null )
@@ -1849,6 +1869,7 @@ private void bindBackupButtonLogic(MainActivity mainAct, File backupsDir, File i
18491869
18501870 isBackupInProgress = true ;
18511871 btnAdvancedBackup .setText (getString (R .string .install_msg_compressing ));
1872+ btnAdvancedBackup .startProgress ();
18521873 Snackbar .make (v , getString (R .string .install_msg_creating_backup ), Snackbar .LENGTH_LONG ).show ();
18531874
18541875 new Thread (() -> {
@@ -1917,14 +1938,14 @@ private void bindBackupButtonLogic(MainActivity mainAct, File backupsDir, File i
19171938 prefs .edit ().putInt ("backup_daily_id" , currentId - 1 ).apply ();
19181939 }
19191940 isBackupInProgress = false ;
1920- btnAdvancedBackup .setText (getString (R .string .install_btn_backup ));
1941+ btnAdvancedBackup .setText (getString (R .string .install_btn_backup )); btnAdvancedBackup . stopProgress ();
19211942 updateDynamicButtons ();
19221943 disableSystemProtection ();
19231944 });
19241945 } catch (Exception e ) {
19251946 mainAct .runOnUiThread (() -> {
19261947 isBackupInProgress = false ;
1927- btnAdvancedBackup .setText (getString (R .string .install_btn_backup ));
1948+ btnAdvancedBackup .setText (getString (R .string .install_btn_backup )); btnAdvancedBackup . stopProgress ();
19281949 Snackbar .make (getView (), getString (R .string .install_msg_backup_error , e .getMessage ()), Snackbar .LENGTH_LONG ).show ();
19291950 updateDynamicButtons ();
19301951 disableSystemProtection ();
@@ -2131,6 +2152,12 @@ private void refreshRestoreButtonLogic() {
21312152
21322153 btnAdvancedRestore .setEnabled (false );
21332154 btnAdvancedRestore .setText (getString (R .string .install_status_restoring ));
2155+ btnAdvancedRestore .startProgress ();
2156+ if (restoreLogPanel != null ) {
2157+ restoreLogPanel .setVisibility (View .VISIBLE );
2158+ if (restoreLogText != null ) restoreLogText .setText ("" );
2159+ if (restoreLogResult != null ) restoreLogResult .setText ("" );
2160+ }
21342161 File iiabRootDir = new File (requireContext ().getFilesDir (), "rootfs" );
21352162 TarExtractor tarExtractor = new TarExtractor ();
21362163
@@ -2144,6 +2171,8 @@ public void onComplete(String destDir) {
21442171 btnAdvancedRestore .setEnabled (true );
21452172 btnAdvancedRestore .setText (getString (R .string .install_btn_restore ));
21462173 Snackbar .make (getView (), R .string .install_success_restore , Snackbar .LENGTH_LONG ).show ();
2174+ if (restoreLogResult != null ) { restoreLogResult .setText ("\u2713 " ); restoreLogResult .setTextColor (ContextCompat .getColor (requireContext (), R .color .status_success )); }
2175+ btnAdvancedRestore .stopProgress ();
21472176 updateDynamicButtons ();
21482177 });
21492178 }
@@ -2156,19 +2185,67 @@ public void onError(String error) {
21562185 btnAdvancedRestore .setEnabled (true );
21572186 btnAdvancedRestore .setText (getString (R .string .install_btn_restore ));
21582187 Snackbar .make (getView (), getString (R .string .install_msg_restore_failed ) + " " + error , Snackbar .LENGTH_LONG ).show ();
2188+ if (restoreLogResult != null ) { restoreLogResult .setText ("\u2717 " ); restoreLogResult .setTextColor (ContextCompat .getColor (requireContext (), R .color .status_warning )); }
2189+ btnAdvancedRestore .stopProgress ();
21592190 updateDynamicButtons ();
21602191 });
21612192 }
2193+
2194+ @ Override
2195+ public void onProgress (String line ) {
2196+ if (restoreLogText == null ) return ;
2197+ restoreLogText .append (line + "\n " );
2198+ if (restoreLogScroll != null ) {
2199+ restoreLogScroll .post (() -> restoreLogScroll .fullScroll (View .FOCUS_DOWN ));
2200+ }
2201+ }
21622202 });
21632203 });
21642204 }
21652205 }
21662206
2207+ private void startImportSpinner () {
2208+ stopImportSpinner ();
2209+ importSpinnerFrame = 0 ;
2210+ importSpinnerHandler = new android .os .Handler (android .os .Looper .getMainLooper ());
2211+ final Runnable r = new Runnable () {
2212+ @ Override public void run () {
2213+ if (btnImportBackup != null ) {
2214+ String f = IMPORT_SPINNER [importSpinnerFrame ++ % IMPORT_SPINNER .length ];
2215+ btnImportBackup .setText (getString (R .string .install_msg_importing ) + " " + f );
2216+ }
2217+ if (importSpinnerHandler != null ) importSpinnerHandler .postDelayed (this , 90 );
2218+ }
2219+ };
2220+ importSpinnerHandler .post (r );
2221+ }
2222+
2223+ private void stopImportSpinner () {
2224+ if (importSpinnerHandler != null ) {
2225+ importSpinnerHandler .removeCallbacksAndMessages (null );
2226+ importSpinnerHandler = null ;
2227+ }
2228+ }
2229+
2230+ /** Best-effort original filename from a SAF content:// URI (DISPLAY_NAME), or null. */
2231+ private String queryDisplayName (Uri uri ) {
2232+ try (android .database .Cursor c = requireContext ().getContentResolver ()
2233+ .query (uri , new String []{android .provider .OpenableColumns .DISPLAY_NAME }, null , null , null )) {
2234+ if (c != null && c .moveToFirst ()) {
2235+ int idx = c .getColumnIndex (android .provider .OpenableColumns .DISPLAY_NAME );
2236+ if (idx >= 0 ) return c .getString (idx );
2237+ }
2238+ } catch (Exception e ) {
2239+ Log .w (TAG , "queryDisplayName failed: " + e .getMessage ());
2240+ }
2241+ return null ;
2242+ }
2243+
21672244 private void importBackupSafely (Uri sourceUri ) {
21682245 isImporting = true ;
21692246 updateDynamicButtons ();
21702247 btnImportBackup .setEnabled (false );
2171- btnImportBackup . setText ( getString ( R . string . install_msg_importing ) );
2248+ startImportSpinner ( );
21722249 Snackbar .make (getView (), getString (R .string .install_msg_importing ), Snackbar .LENGTH_LONG ).show ();
21732250
21742251 new Thread (() -> {
@@ -2177,7 +2254,14 @@ private void importBackupSafely(Uri sourceUri) {
21772254 File backupsDir = new File (requireContext ().getFilesDir (), "rootfs/backups" );
21782255 if (!backupsDir .exists ()) backupsDir .mkdirs ();
21792256
2180- String fileName = "imported_backup_" + System .currentTimeMillis () + ".tar.gz" ;
2257+ // Keep the imported file's EXACT name; disambiguate with -1/-2/... on collision.
2258+ String desiredName = queryDisplayName (sourceUri );
2259+ java .util .Set <String > existingNames = new java .util .HashSet <>();
2260+ File [] existingFiles = backupsDir .listFiles ();
2261+ if (existingFiles != null ) {
2262+ for (File f : existingFiles ) existingNames .add (f .getName ());
2263+ }
2264+ String fileName = org .iiab .controller .backup .domain .BackupNameResolver .resolve (desiredName , existingNames );
21812265 File destFile = new File (backupsDir , fileName );
21822266
21832267 InputStream is = requireContext ().getContentResolver ().openInputStream (sourceUri );
@@ -2194,6 +2278,7 @@ private void importBackupSafely(Uri sourceUri) {
21942278 if (getActivity () != null ) {
21952279 getActivity ().runOnUiThread (() -> {
21962280 isImporting = false ;
2281+ stopImportSpinner ();
21972282 btnImportBackup .setEnabled (true );
21982283 btnImportBackup .setText (getString (R .string .install_btn_import_backup ));
21992284 selectedBackupFile = fileName ;
@@ -2205,6 +2290,7 @@ private void importBackupSafely(Uri sourceUri) {
22052290 if (getActivity () != null ) {
22062291 getActivity ().runOnUiThread (() -> {
22072292 isImporting = false ;
2293+ stopImportSpinner ();
22082294 updateDynamicButtons ();
22092295 btnImportBackup .setEnabled (true );
22102296 btnImportBackup .setText (getString (R .string .install_btn_import_backup ));
0 commit comments