diff --git a/build/android/res/layout/dialog_template.xml b/build/android/res/layout/dialog_template.xml
new file mode 100644
index 000000000..a89473852
--- /dev/null
+++ b/build/android/res/layout/dialog_template.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build/android/res/layout/instruction_dialog.xml b/build/android/res/layout/instruction_dialog.xml
deleted file mode 100644
index 6498eee1d..000000000
--- a/build/android/res/layout/instruction_dialog.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/build/android/res/layout/rate_layout.xml b/build/android/res/layout/rate_layout.xml
index a471911d6..b5c8b0814 100644
--- a/build/android/res/layout/rate_layout.xml
+++ b/build/android/res/layout/rate_layout.xml
@@ -1,22 +1,22 @@
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+ android:layout_gravity="center"
+ android:text="@string/rta_dialog_title" />
@@ -31,11 +31,11 @@
+ android:enabled="false"
+ android:text="@string/rate_submit"
+ android:textColor="#00ced1" />
+
\ No newline at end of file
diff --git a/build/android/res/values-ru/strings.xml b/build/android/res/values-ru/strings.xml
index 0463b258f..e7e45e635 100644
--- a/build/android/res/values-ru/strings.xml
+++ b/build/android/res/values-ru/strings.xml
@@ -6,8 +6,8 @@
Во время загрузки не закрывайте приложение. \nИгра запустится через несколько секунд…
Подготовка к обновлению…
Копирование…
- Загрузка
- Загрузка файлов игры…
+ Загрузка MultiCraft
+ Осталось меньше минуты…
Оцените MultiCraft Beta — Бесплатный Майнер!
@@ -29,8 +29,8 @@
Игнорировать
- Привет! После загрузки игры нажмите Play.\nЧтобы поставить блок, быстро тапните 2 раза там, куда хотите его поставить!\nЧтобы разъединить блоки в инвентаре, возьмите блок в руку и одновременно прикоснитесь пальцами к двум ячейкам. Вставится половина стопки. Нажмите на другую ячейку и вставится вторая половина!
- Понял
+ Привет! После загрузки игры нажмите Play.\nЧтобы поставить блок, нажмите на экран.\nЧтобы разрушить блок, удерживайте палец на экране.
+ Закрыть
Не напоминать
diff --git a/build/android/res/values/color.xml b/build/android/res/values/color.xml
new file mode 100644
index 000000000..41033dcd6
--- /dev/null
+++ b/build/android/res/values/color.xml
@@ -0,0 +1,4 @@
+
+
+ #80000000
+
\ No newline at end of file
diff --git a/build/android/res/values/strings.xml b/build/android/res/values/strings.xml
index 22f9dc331..21a84c97a 100644
--- a/build/android/res/values/strings.xml
+++ b/build/android/res/values/strings.xml
@@ -13,31 +13,31 @@
Loading…
- Do not stop the application while it is unpacking or updating. The game will start in few seconds.…
+ When unpacking, do not wrap application.\nGame will start in few second…
Preparing to update…
Copying…
- Downloading
- Downloading game data…
+ Downloading MultiCraft
+ Less than 1 minute…
Warning!
Your device\'s memory is too low for seamless playing
Continue
Close
- Lag is possible. Please don\'t rate this game badly.
+ Lags are possible. Please don\'t rate this game badly.
- An update is available!
+ Update now!
Remind me later…
Ignore
- Hello! After loading, select the world and press Play. \nTo place an item, tap and release where you want to put it (or quickly tap twice).\nTo split blocks inside inventory take the block and simultaneously touch the two cells; the first half will be inserted on first cell. Click on another cell for inserting the second half
- Got it
+ Hello! After game loading select the world and press Play. \nTo put a block tap on screen.\nTo destroy a block long tap on screen.
+ Close
Don\'t remind
Not enough space for unzipping.\nPlease free some space on the SD card
OK
-
+
\ No newline at end of file
diff --git a/build/android/src/com/MoNTE48/MultiCraft/MainActivity.java b/build/android/src/com/MoNTE48/MultiCraft/MainActivity.java
index 50b231c06..43cbec974 100644
--- a/build/android/src/com/MoNTE48/MultiCraft/MainActivity.java
+++ b/build/android/src/com/MoNTE48/MultiCraft/MainActivity.java
@@ -1,5 +1,9 @@
package com.MoNTE48.MultiCraft;
+import static com.MoNTE48.MultiCraft.PreferencesHelper.TAG_SHORTCUT_CREATED;
+import static com.MoNTE48.MultiCraft.PreferencesHelper.isCreateShortcut;
+import static com.MoNTE48.MultiCraft.PreferencesHelper.saveSettings;
+
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -14,7 +18,6 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Build;
@@ -28,7 +31,6 @@ import android.widget.TextView;
import com.MoNTE48.MultiCraft.Utilities.IUtilitiesCallback;
public class MainActivity extends Activity implements IUtilitiesCallback {
- public static final String SHORTCUT_NAME = "shortcut";
private final String TAG = MainActivity.class.getName();
public final String FILES = "Files.zip";
public final String NOMEDIA = ".nomedia";
@@ -49,18 +51,13 @@ public class MainActivity extends Activity implements IUtilitiesCallback {
util = new Utilities(this);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
registerReceivers();
- SharedPreferences settings = getSharedPreferences(SHORTCUT_NAME, 0);
- boolean doNotExecute = settings.getBoolean("doNotExecute", false);
- if (!doNotExecute)
- addShortcut();
-
}
@Override
protected void onStart() {
super.onStart();
if (util.getTotalMemoryInMB() < 800 || util.getCoresCount() < 2) {
- util.showMemoryDialog(MainActivity.this);
+ util.showMemoryDialog();
} else {
init();
}
@@ -80,10 +77,7 @@ public class MainActivity extends Activity implements IUtilitiesCallback {
}
private void addShortcut() {
- SharedPreferences settings = getSharedPreferences(SHORTCUT_NAME, 0);
- SharedPreferences.Editor editor = settings.edit();
- editor.putBoolean("doNotExecute", true);
- editor.apply();
+ saveSettings(this, TAG_SHORTCUT_CREATED, false);
Intent shortcutIntent = new Intent(getApplicationContext(),
MainActivity.class);
shortcutIntent.setAction(Intent.ACTION_MAIN);
@@ -100,6 +94,9 @@ public class MainActivity extends Activity implements IUtilitiesCallback {
@SuppressWarnings("deprecation")
public void init() {
+ PreferencesHelper.loadSettings(this);
+ if (isCreateShortcut())
+ addShortcut();
mProgressTextView = (TextView) findViewById(R.id.progress_textView);
mProgressBar = (ProgressBar) findViewById(R.id.PB1);
Drawable draw;
@@ -116,6 +113,11 @@ public class MainActivity extends Activity implements IUtilitiesCallback {
checkVersion();
}
+ @Override
+ public void finishMe() {
+ finish();
+ }
+
private void registerReceivers() {
myBroadcastReceiver = new MyBroadcastReceiver();
myBroadcastReceiver_Update = new MyBroadcastReceiver_Update();
@@ -153,7 +155,7 @@ public class MainActivity extends Activity implements IUtilitiesCallback {
+ "games/MultiCraft II", unzipLocation + "tmp");
break;
case CURRENT:
- startBetweenActivity();
+ startNativeActivity();
break;
}
} else {
@@ -162,7 +164,7 @@ public class MainActivity extends Activity implements IUtilitiesCallback {
}
}
- private void startBetweenActivity() {
+ private void startNativeActivity() {
showSpinnerDialog(R.string.loading);
new Thread(new Runnable() {
public void run() {
@@ -197,7 +199,7 @@ public class MainActivity extends Activity implements IUtilitiesCallback {
public void onReceive(Context context, Intent intent) {
String result = intent.getStringExtra(UnzipService.EXTRA_KEY_OUT);
if ("Success".equals(result))
- startBetweenActivity();
+ startNativeActivity();
}
}
@@ -279,7 +281,7 @@ public class MainActivity extends Activity implements IUtilitiesCallback {
Log.e(TAG, e.getMessage());
}
} else
- util.showNotEnoughSpaceDialog(MainActivity.this);
+ util.showNotEnoughSpaceDialog();
}
private void copyAssets(String zipName) {
@@ -311,9 +313,9 @@ public class MainActivity extends Activity implements IUtilitiesCallback {
@Override
protected void onDestroy() {
+ super.onDestroy();
dismissProgressDialog();
unregisterReceiver(myBroadcastReceiver);
unregisterReceiver(myBroadcastReceiver_Update);
- super.onDestroy();
}
-}
+}
\ No newline at end of file
diff --git a/build/android/src/com/MoNTE48/MultiCraft/PreferencesHelper.java b/build/android/src/com/MoNTE48/MultiCraft/PreferencesHelper.java
new file mode 100644
index 000000000..748dc1934
--- /dev/null
+++ b/build/android/src/com/MoNTE48/MultiCraft/PreferencesHelper.java
@@ -0,0 +1,44 @@
+package com.MoNTE48.MultiCraft;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+public class PreferencesHelper {
+ private static final String SETTINGS = "settings";
+ public static final String TAG_SHORTCUT_CREATED = "createShortcut";
+ public static final String TAG_HELP_SHOWED = "showHelp";
+ private static boolean createShortcut, showHelp;
+
+ public static boolean isCreateShortcut() {
+ return createShortcut;
+ }
+
+ public static boolean isShowHelp() {
+ return showHelp;
+ }
+
+ public static void loadSettings(Context c) {
+
+ SharedPreferences settings = c.getSharedPreferences(SETTINGS,
+ Context.MODE_PRIVATE);
+ if (settings.getAll().size() == 0) {
+ SharedPreferences.Editor editor = settings.edit();
+ editor.clear();
+ editor.commit();
+ createShortcut = true;
+ showHelp = true;
+ } else {
+ createShortcut = settings.getBoolean(TAG_SHORTCUT_CREATED, true);
+ showHelp = settings.getBoolean(TAG_HELP_SHOWED, true);
+ }
+
+ }
+
+ public static void saveSettings(Context c, String tag, boolean bool) {
+ SharedPreferences settings = c.getSharedPreferences(SETTINGS,
+ Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean(tag, bool);
+ editor.commit();
+ }
+}
diff --git a/build/android/src/com/MoNTE48/MultiCraft/UnzipService.java b/build/android/src/com/MoNTE48/MultiCraft/UnzipService.java
index 5692e9078..0a53bbd61 100644
--- a/build/android/src/com/MoNTE48/MultiCraft/UnzipService.java
+++ b/build/android/src/com/MoNTE48/MultiCraft/UnzipService.java
@@ -48,7 +48,6 @@ public class UnzipService extends IntentService {
String file = intent.getStringExtra(EXTRA_KEY_IN_FILE);
String location = intent.getStringExtra(EXTRA_KEY_IN_LOCATION);
// Displays the progress bar for the first time.
- mBuilder.setProgress(100, 0, false);
int id = 1;
mNotifyManager.notify(id, mBuilder.build());
int per = 0;
@@ -71,8 +70,7 @@ public class UnzipService extends IntentService {
intentUpdate.addCategory(Intent.CATEGORY_DEFAULT);
intentUpdate.putExtra(EXTRA_KEY_UPDATE, progress);
sendBroadcast(intentUpdate);
- mBuilder.setProgress(100, progress, false);
- mNotifyManager.notify(id, mBuilder.build());
+// mNotifyManager.notify(id, mBuilder.build());
FileOutputStream f_out = new FileOutputStream(location
+ ze.getName());
byte[] buffer = new byte[8192];
@@ -93,9 +91,6 @@ public class UnzipService extends IntentService {
} catch (IOException e) {
Log.e(TAG, e.getLocalizedMessage());
}
- // mBuilder.setContentText(getString(R.string.complete));
- // mBuilder.setProgress(0, 0, false);
- // mNotifyManager.notify(id, mBuilder.build());
mNotifyManager.cancel(id);
Intent intentResponse = new Intent();
intentResponse.setAction(ACTION_MyIntentService);
diff --git a/build/android/src/com/MoNTE48/MultiCraft/Utilities.java b/build/android/src/com/MoNTE48/MultiCraft/Utilities.java
index 3adcbc74f..57084918f 100644
--- a/build/android/src/com/MoNTE48/MultiCraft/Utilities.java
+++ b/build/android/src/com/MoNTE48/MultiCraft/Utilities.java
@@ -1,5 +1,9 @@
package com.MoNTE48.MultiCraft;
+import static com.MoNTE48.MultiCraft.PreferencesHelper.TAG_HELP_SHOWED;
+import static com.MoNTE48.MultiCraft.PreferencesHelper.isShowHelp;
+import static com.MoNTE48.MultiCraft.PreferencesHelper.saveSettings;
+
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
@@ -11,16 +15,18 @@ import mobi.MultiCraft.R;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ActivityManager;
-import android.app.AlertDialog;
+import android.app.Dialog;
import android.content.Context;
-import android.content.DialogInterface;
-import android.content.SharedPreferences;
+import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
import android.util.Log;
-import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.Window;
+import android.widget.Button;
+import android.widget.TextView;
import android.widget.Toast;
import com.MoNTE48.RateME.RateThisApp;
@@ -30,22 +36,25 @@ import com.winsontan520.wversionmanager.library.WVersionManager;
* Helpful utilities used in MainActivity
*/
public class Utilities {
+ private Button positive, negative;
+ private Dialog dialog;
+
public enum VERSIONS {
CURRENT, OLD
}
private final String TAG = Utilities.class.getName();
private Context mContext;
- public static final String PREFS_NAME = "ShowFirstTime";
public final String STABLE_VER = "1.0.1";
/**
- * Callback for MainActivity init method
- *
+ * Callback for MainActivity init and finishMe methods
*/
public interface IUtilitiesCallback {
void init();
+
+ void finishMe();
}
private IUtilitiesCallback callerActivity;
@@ -55,38 +64,45 @@ public class Utilities {
callerActivity = (IUtilitiesCallback) activity;
}
+ private void dialogInit(int panel, int positiveBtn, int negativeBtn,
+ int messageText) {
+ dialog = new Dialog(mContext);
+ dialog.requestWindowFeature(panel);
+ dialog.setContentView(R.layout.dialog_template);
+ positive = (Button) dialog.findViewById(R.id.positive);
+ negative = (Button) dialog.findViewById(R.id.negative);
+ TextView message = (TextView) dialog.findViewById(R.id.message);
+ positive.setText(positiveBtn);
+ negative.setText(negativeBtn);
+ message.setText(messageText);
+ dialog.setCancelable(false);
+ dialog.getWindow().setBackgroundDrawable(
+ new ColorDrawable(R.color.semi_transparent));
+ }
+
@SuppressLint("InflateParams")
public void showHelpDialog() {
- LayoutInflater inflater = LayoutInflater.from(mContext);
- View messageView = inflater.inflate(R.layout.instruction_dialog, null,
- false);
- final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
- builder.setView(messageView);
- builder.setPositiveButton(R.string.ok,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- RateThisApp.showRateDialogIfNeeded(mContext);
- }
- });
- builder.setNegativeButton(R.string.forget,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- String checkBoxResult = "checked";
- SharedPreferences settings = mContext
- .getSharedPreferences(PREFS_NAME, 0);
- SharedPreferences.Editor editor = settings.edit();
- editor.putString("skipMessage", checkBoxResult);
- editor.apply();
- RateThisApp.showRateDialogIfNeeded(mContext);
- }
- });
- SharedPreferences settings = mContext.getSharedPreferences(PREFS_NAME,
- 0);
- String skipMessage = settings.getString("skipMessage", "NOT checked");
- if (!"checked".equalsIgnoreCase(skipMessage))
- builder.show();
+ dialogInit(Window.FEATURE_NO_TITLE, R.string.ok, R.string.forget,
+ R.string.dialog_instruction);
+ positive.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dialog.dismiss();
+ RateThisApp.showRateDialogIfNeeded(mContext);
+ }
+ });
+ negative.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ dialog.dismiss();
+ saveSettings(mContext, TAG_HELP_SHOWED, false);
+ RateThisApp.showRateDialogIfNeeded(mContext);
+ }
+ });
+ if (isShowHelp()) {
+ dialog.show();
+ }
}
public void showVersionDialog() {
@@ -103,43 +119,43 @@ public class Utilities {
.getResources().getText(R.string.update_ignore));
}
- public void showMemoryDialog(final Activity activity) {
- final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
- builder.setTitle(R.string.memory_title);
- builder.setMessage(R.string.memory_warning);
- builder.setPositiveButton(R.string.memory_continue,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- Toast.makeText(mContext, R.string.memory_lags,
- Toast.LENGTH_SHORT).show();
- callerActivity.init();
- }
- });
- builder.setNegativeButton(R.string.memory_close,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- activity.finish();
- }
- });
- builder.setCancelable(false);
- builder.show();
+ public void showMemoryDialog() {
+ dialogInit(Window.FEATURE_OPTIONS_PANEL, R.string.memory_continue,
+ R.string.memory_close, R.string.memory_warning);
+ positive.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dialog.dismiss();
+ Toast.makeText(mContext, R.string.memory_lags,
+ Toast.LENGTH_SHORT).show();
+ callerActivity.init();
+ }
+ });
+ negative.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ dialog.dismiss();
+ callerActivity.finishMe();
+ }
+ });
+ dialog.show();
}
- public void showNotEnoughSpaceDialog(final Activity activity) {
- final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
- builder.setTitle(R.string.memory_title);
- builder.setMessage(R.string.not_enough_space);
- builder.setPositiveButton(R.string.space_ok,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- activity.finish();
- }
- });
- builder.setCancelable(false);
- builder.show();
+ public void showNotEnoughSpaceDialog() {
+ dialogInit(Window.FEATURE_OPTIONS_PANEL, R.string.space_ok,
+ R.string.memory_close, R.string.not_enough_space);
+ negative.setVisibility(View.GONE);
+ positive.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dialog.dismiss();
+ Toast.makeText(mContext, R.string.memory_lags,
+ Toast.LENGTH_SHORT).show();
+ callerActivity.init();
+ }
+ });
+ dialog.show();
}
public long getTotalMemoryInMB() {
diff --git a/build/android/src/com/MoNTE48/RateME/RateThisApp.java b/build/android/src/com/MoNTE48/RateME/RateThisApp.java
index eaa66141f..cc02a198b 100644
--- a/build/android/src/com/MoNTE48/RateME/RateThisApp.java
+++ b/build/android/src/com/MoNTE48/RateME/RateThisApp.java
@@ -8,6 +8,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
+import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.util.Log;
import android.widget.RatingBar;
@@ -119,6 +120,8 @@ public class RateThisApp {
}
}
});
+ dialog.getWindow().setBackgroundDrawable(
+ new ColorDrawable(R.color.semi_transparent));
dialog.show();
}
diff --git a/build/android/src/net/minetest/minetest/MtNativeActivity.java b/build/android/src/net/minetest/minetest/MtNativeActivity.java
index aad19b9eb..736bc651c 100644
--- a/build/android/src/net/minetest/minetest/MtNativeActivity.java
+++ b/build/android/src/net/minetest/minetest/MtNativeActivity.java
@@ -1,8 +1,11 @@
package net.minetest.minetest;
+import static com.MoNTE48.MultiCraft.PreferencesHelper.isShowHelp;
+
+import java.util.concurrent.ScheduledExecutorService;
+
import android.app.NativeActivity;
import android.content.Intent;
-import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.view.WindowManager;
@@ -14,28 +17,53 @@ import com.MoNTE48.RateME.RateThisApp;
public class MtNativeActivity extends NativeActivity implements
IUtilitiesCallback {
+ private ScheduledExecutorService scheduler;
+ private int m_MessagReturnCode;
+ private String m_MessageReturnValue;
+ private Utilities util;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ init();
+ }
+
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == 101) {
+ if (resultCode == RESULT_OK) {
+ String text = data.getStringExtra("text");
+ m_MessagReturnCode = 0;
+ m_MessageReturnValue = text;
+ } else {
+ m_MessagReturnCode = 1;
+ }
+ }
+ }
+
+ @Override
+ public void init() {
m_MessagReturnCode = -1;
m_MessageReturnValue = "";
RateThisApp.onStart(this);
+ util = new Utilities(MtNativeActivity.this);
startDialogs();
}
+ @Override
+ public void finishMe() {
+ finish();
+ }
+
private void startDialogs() {
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
- Utilities util = new Utilities(MtNativeActivity.this);
+
util.showHelpDialog();
- SharedPreferences settings = MtNativeActivity.this
- .getSharedPreferences(Utilities.PREFS_NAME, 0);
- String skipMessage = settings.getString("skipMessage",
- "NOT checked");
- if ("checked".equalsIgnoreCase(skipMessage)) {
+
+ if (!isShowHelp()) {
if (RateThisApp.shouldShowRateDialog()) {
RateThisApp
.showRateDialogIfNeeded(MtNativeActivity.this);
@@ -87,19 +115,6 @@ public class MtNativeActivity extends NativeActivity implements
return getResources().getDisplayMetrics().heightPixels;
}
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == 101) {
- if (resultCode == RESULT_OK) {
- String text = data.getStringExtra("text");
- m_MessagReturnCode = 0;
- m_MessageReturnValue = text;
- } else {
- m_MessagReturnCode = 1;
- }
- }
- }
-
static {
System.loadLibrary("openal");
System.loadLibrary("ogg");
@@ -108,11 +123,4 @@ public class MtNativeActivity extends NativeActivity implements
System.loadLibrary("crypto");
}
- private int m_MessagReturnCode;
- private String m_MessageReturnValue;
-
- @Override
- public void init() {
-
- }
}
\ No newline at end of file