投稿画面のmoreメニューに「下書きから復元」を追加

This commit is contained in:
tateisu 2017-05-18 01:28:03 +09:00
parent 819a9ecb8a
commit 9379b2a9fe
8 changed files with 469 additions and 29 deletions

View File

@ -9,8 +9,8 @@ android {
applicationId "jp.juggler.subwaytooter"
minSdkVersion 21
targetSdkVersion 25
versionCode 58
versionName "0.5.8"
versionCode 59
versionName "0.5.9"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

View File

@ -38,6 +38,7 @@ import android.widget.TextView;
import org.apache.commons.io.IOUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
@ -59,6 +60,7 @@ import jp.juggler.subwaytooter.api.entity.TootStatus;
import jp.juggler.subwaytooter.dialog.DlgConfirm;
import jp.juggler.subwaytooter.table.AcctColor;
import jp.juggler.subwaytooter.table.AcctSet;
import jp.juggler.subwaytooter.table.PostDraft;
import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.dialog.ActionsDialog;
import jp.juggler.subwaytooter.util.HTMLDecoder;
@ -67,13 +69,14 @@ import jp.juggler.subwaytooter.view.MyEditText;
import jp.juggler.subwaytooter.view.MyNetworkImageView;
import jp.juggler.subwaytooter.util.PostAttachment;
import jp.juggler.subwaytooter.util.Utils;
import okhttp3.Call;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okio.BufferedSink;
public class ActPost extends AppCompatActivity implements View.OnClickListener, PostAttachment.Callback {
static final LogCategory log = new LogCategory( "ActPost" );
@ -148,20 +151,19 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
break;
case R.id.btnPost:
performPost( false,false );
performPost( false, false );
break;
case R.id.btnRemoveReply:
removeReply();
break;
case R.id.btnClear:
performClear();
case R.id.btnMore:
performMore();
break;
}
}
private static final int REQUEST_CODE_ATTACHMENT = 1;
private static final int REQUEST_CODE_CAMERA = 2;
@ -211,6 +213,11 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
super.onActivityResult( requestCode, resultCode, data );
}
@Override public void onBackPressed(){
saveDraft();
super.onBackPressed();
}
SharedPreferences pref;
ArrayList< PostAttachment > attachment_list;
AppState app_state;
@ -505,9 +512,8 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
setContentView( R.layout.act_post );
Styler.fixHorizontalPadding(findViewById( R.id.llContent ));
Styler.fixHorizontalMargin(findViewById( R.id.llFooterBar ));
Styler.fixHorizontalPadding( findViewById( R.id.llContent ) );
Styler.fixHorizontalMargin( findViewById( R.id.llFooterBar ) );
formRoot = findViewById( R.id.viewRoot );
scrollView = (ScrollView) findViewById( R.id.scrollView );
@ -590,7 +596,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
scrollView.getViewTreeObserver().addOnScrollChangedListener( scroll_listener );
View v = findViewById( R.id.btnClear );
View v = findViewById( R.id.btnMore );
v.setOnClickListener( this );
}
@ -800,14 +806,13 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
}
void openAttachment(){
int permissionCheck = ContextCompat.checkSelfPermission( this, Manifest.permission.WRITE_EXTERNAL_STORAGE );
if( permissionCheck != PackageManager.PERMISSION_GRANTED ){
preparePermission();
return;
}
ActionsDialog a = new ActionsDialog();
a.addAction( getString( R.string.image_pick ), new Runnable() {
@Override public void run(){
@ -823,7 +828,6 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
}
private void performAttachment(){
if( attachment_list != null && attachment_list.size() >= 4 ){
@ -836,7 +840,6 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
return;
}
// SAFのIntentで開く
try{
Intent intent = new Intent( Intent.ACTION_OPEN_DOCUMENT );
@ -1235,8 +1238,22 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
///////////////////////////////////////////////////////////////////////////////////////
private void performClear(){
private void performMore(){
ActionsDialog dialog = new ActionsDialog();
final ArrayList< JSONObject > list_draft = PostDraft.loadList( 20 );
if( ! list_draft.isEmpty() ){
dialog.addAction(
getString( R.string.restore_draft )
, new Runnable() {
@Override public void run(){
openDraftPicker( list_draft );
}
}
);
}
dialog.addAction(
getString( R.string.clear_text )
, new Runnable() {
@ -1323,6 +1340,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
.setNegativeButton( R.string.cancel, null )
.setPositiveButton( R.string.ok, new DialogInterface.OnClickListener() {
@Override public void onClick( DialogInterface dialog, int which ){
//noinspection ConstantConditions
performPost( true, bConfirmAccount );
}
} )
@ -1462,4 +1480,266 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
in_reply_to_image = null;
showReplyTo();
}
/////////////////////////////////////////////////
static final String DRAFT_CONTENT = "content";
static final String DRAFT_CONTENT_WARNING = "content_warning";
static final String DRAFT_CONTENT_WARNING_CHECK = "content_warning_check";
static final String DRAFT_NSFW_CHECK = "nsfw_check";
static final String DRAFT_VISIBILITY = "visibility";
static final String DRAFT_ACCOUNT_DB_ID = "account_db_id";
static final String DRAFT_ATTACHMENT_LIST = "attachment_list";
static final String DRAFT_REPLY_ID = "reply_id";
static final String DRAFT_REPLY_TEXT = "reply_text";
static final String DRAFT_REPLY_IMAGE = "reply_image";
private void saveDraft(){
String content = etContent.getText().toString();
String content_warning = etContentWarning.getText().toString();
if( TextUtils.isEmpty( content.trim() ) && TextUtils.isEmpty( content_warning.trim() ) ){
log.d( "saveDraft: dont save empty content" );
return;
}
try{
JSONArray tmp_attachment_list = new JSONArray();
if( attachment_list != null ){
for( PostAttachment pa : attachment_list ){
if( pa.attachment != null ) tmp_attachment_list.put( pa.attachment.json );
}
}
JSONObject json = new JSONObject();
json.put( DRAFT_CONTENT, content );
json.put( DRAFT_CONTENT_WARNING, content_warning );
json.put( DRAFT_CONTENT_WARNING_CHECK, cbContentWarning.isChecked() );
json.put( DRAFT_NSFW_CHECK, cbNSFW.isChecked() );
json.put( DRAFT_VISIBILITY, visibility );
json.put( DRAFT_ACCOUNT_DB_ID, ( account == null ? - 1L : account.db_id ) );
json.put( DRAFT_ATTACHMENT_LIST, tmp_attachment_list );
json.put( DRAFT_REPLY_ID, in_reply_to_id );
json.put( DRAFT_REPLY_TEXT, in_reply_to_text );
json.put( DRAFT_REPLY_IMAGE, in_reply_to_image );
PostDraft.save( System.currentTimeMillis(), json );
}catch( Throwable ex ){
ex.printStackTrace();
}
}
private void openDraftPicker( @NonNull ArrayList< JSONObject > list_draft ){
ActionsDialog dialog = new ActionsDialog();
for( JSONObject o : list_draft ){
final JSONObject draft = o;
String cw = draft.optString( DRAFT_CONTENT_WARNING );
String c = draft.optString( DRAFT_CONTENT );
StringBuilder sb = new StringBuilder();
if( ! TextUtils.isEmpty( cw.trim() ) ){
sb.append( cw );
}
if( ! TextUtils.isEmpty( c.trim() ) ){
if( sb.length() > 0 ) sb.append( "\n" );
sb.append( c );
}
String caption = sb.toString();
dialog.addAction(
caption
, new Runnable() {
@Override public void run(){
restoreDraft( draft );
}
}
);
}
dialog.show( this, getString( R.string.select_draft ) );
}
static boolean check_exist( String url ){
try{
Request request = new Request.Builder().url( url ).build();
Call call = App1.ok_http_client.newCall( request );
return call.execute().isSuccessful();
}catch( Throwable ex ){
ex.printStackTrace();
}
return false;
}
private void restoreDraft( final JSONObject draft ){
final ProgressDialog progress = new ProgressDialog( this );
final AsyncTask< Void, String, String > task = new AsyncTask< Void, String, String >() {
ArrayList< String > list_warning = new ArrayList<>();
SavedAccount account;
@Override protected String doInBackground( Void... params ){
String content = draft.optString( DRAFT_CONTENT );
String content_warning = draft.optString( DRAFT_CONTENT_WARNING );
boolean content_warning_checked = draft.optBoolean( DRAFT_CONTENT_WARNING_CHECK );
boolean nsfw_checked = draft.optBoolean( DRAFT_NSFW_CHECK );
long account_db_id = draft.optLong( DRAFT_ACCOUNT_DB_ID );
JSONArray tmp_attachment_list = draft.optJSONArray( DRAFT_ATTACHMENT_LIST );
long reply_id = draft.optLong( DRAFT_REPLY_ID, in_reply_to_id );
String reply_text = draft.optString( DRAFT_REPLY_TEXT, in_reply_to_text );
String reply_image = draft.optString( DRAFT_REPLY_IMAGE, in_reply_to_image );
account = SavedAccount.loadAccount( log, account_db_id );
if( account == null ){
list_warning.add( getString( R.string.account_in_draft_is_lost ) );
try{
for( int i = 0, ie = tmp_attachment_list.length() ; i < ie ; ++ i ){
TootAttachment ta = TootAttachment.parse( log, tmp_attachment_list.optJSONObject( i ) );
if( ta != null ){
content = content.replace( ta.text_url, "" );
}
}
tmp_attachment_list = new JSONArray();
draft.put( DRAFT_ATTACHMENT_LIST, tmp_attachment_list );
draft.put( DRAFT_CONTENT, content );
draft.remove( DRAFT_REPLY_ID );
draft.remove( DRAFT_REPLY_TEXT );
draft.remove( DRAFT_REPLY_IMAGE );
}catch( JSONException ignored ){
}
return "OK";
}
// アカウントがあるなら基本的にはすべての情報を復元できるはずだがいくつか確認が必要だ
TootApiClient api_client = new TootApiClient( ActPost.this, new TootApiClient.Callback() {
@Override public boolean isApiCancelled(){
return isCancelled();
}
@Override public void publishApiProgress( final String s ){
Utils.runOnMainThread( new Runnable() {
@Override public void run(){
progress.setMessage( s );
}
} );
}
} );
api_client.setAccount( account );
if( in_reply_to_id != - 1L ){
TootApiResult result = api_client.request( "/api/v1/statuses/" + in_reply_to_id );
if( isCancelled() ) return null;
if( result != null && result.object == null ){
list_warning.add( getString( R.string.reply_to_in_draft_is_lost ) );
draft.remove( DRAFT_REPLY_ID );
draft.remove( DRAFT_REPLY_TEXT );
draft.remove( DRAFT_REPLY_IMAGE );
}
}
try{
boolean isSomeAttachmentRemoved = false;
for( int i = tmp_attachment_list.length() - 1 ; i >= 0 ; -- i ){
if( isCancelled() ) return null;
TootAttachment ta = TootAttachment.parse( log, tmp_attachment_list.optJSONObject( i ) );
if( ta == null ){
isSomeAttachmentRemoved = true;
tmp_attachment_list.remove( i );
}else if( ! check_exist( ta.url ) ){
isSomeAttachmentRemoved = true;
tmp_attachment_list.remove( i );
content = content.replace( ta.text_url, "" );
}
}
if( isSomeAttachmentRemoved ){
list_warning.add( getString( R.string.attachment_in_draft_is_lost ) );
draft.put( DRAFT_ATTACHMENT_LIST, tmp_attachment_list );
draft.put( DRAFT_CONTENT, content );
}
}catch( JSONException ex ){
ex.printStackTrace();
}
return "OK";
}
@Override protected void onCancelled( String result ){
super.onCancelled( result );
}
@Override protected void onPostExecute( String result ){
progress.dismiss();
if( isCancelled() || result == null ){
// cancelled.
return;
}
String content = draft.optString( DRAFT_CONTENT );
String content_warning = draft.optString( DRAFT_CONTENT_WARNING );
boolean content_warning_checked = draft.optBoolean( DRAFT_CONTENT_WARNING_CHECK );
boolean nsfw_checked = draft.optBoolean( DRAFT_NSFW_CHECK );
JSONArray tmp_attachment_list = draft.optJSONArray( DRAFT_ATTACHMENT_LIST );
long reply_id = draft.optLong( DRAFT_REPLY_ID, in_reply_to_id );
String reply_text = draft.optString( DRAFT_REPLY_TEXT, in_reply_to_text );
String reply_image = draft.optString( DRAFT_REPLY_IMAGE, in_reply_to_image );
etContent.setText( content );
etContent.setSelection( content.length() );
etContentWarning.setText( content_warning );
etContentWarning.setSelection( content_warning.length() );
cbContentWarning.setChecked( content_warning_checked );
cbNSFW.setChecked( nsfw_checked );
ActPost.this.visibility = visibility;
if( account != null ) setAccount( account );
if( tmp_attachment_list.length() > 0 ){
if( attachment_list != null ){
attachment_list.clear();
}else{
attachment_list = new ArrayList<>();
}
for( int i = 0, ie = tmp_attachment_list.length() ; i < ie ; ++ i ){
TootAttachment ta = TootAttachment.parse( log, tmp_attachment_list.optJSONObject( i ) );
if( ta != null ){
PostAttachment pa = new PostAttachment( ta );
attachment_list.add( pa );
}
}
}
if( reply_id != - 1L ){
in_reply_to_id = reply_id;
in_reply_to_text = reply_text;
in_reply_to_image = reply_image;
}
updateContentWarning();
showMediaAttachment();
showVisibility();
updateTextCount();
showReplyTo();
if( ! list_warning.isEmpty() ){
StringBuilder sb = new StringBuilder();
for( String s : list_warning ){
if( sb.length() > 0 ) sb.append( "\n" );
sb.append( s );
}
new AlertDialog.Builder( ActPost.this )
.setMessage( sb )
.setNeutralButton( R.string.close, null )
.show();
}
}
};
progress.setIndeterminate( true );
progress.setCancelable( true );
progress.setOnCancelListener( new DialogInterface.OnCancelListener() {
@Override public void onCancel( DialogInterface dialog ){
task.cancel( true );
}
} );
progress.show();
task.executeOnExecutor( App1.task_executor );
}
}

View File

@ -38,6 +38,7 @@ import jp.juggler.subwaytooter.table.LogData;
import jp.juggler.subwaytooter.table.MediaShown;
import jp.juggler.subwaytooter.table.MutedWord;
import jp.juggler.subwaytooter.table.NotificationTracking;
import jp.juggler.subwaytooter.table.PostDraft;
import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.table.UserRelation;
import jp.juggler.subwaytooter.util.LogCategory;
@ -52,7 +53,7 @@ public class App1 extends Application {
static final LogCategory log = new LogCategory( "App1" );
static final String DB_NAME = "app_db";
static final int DB_VERSION = 11;
static final int DB_VERSION = 12;
// 2017/4/25 v10 1=>2 SavedAccount に通知設定を追加
// 2017/4/25 v10 1=>2 NotificationTracking テーブルを追加
// 2017/4/29 v20 2=>5 MediaShown,ContentWarningのインデクスが間違っていたので貼り直す
@ -62,6 +63,7 @@ public class App1 extends Application {
// 2017/5/02 v32 8=>9 AcctColor テーブルの追加
// 2017/5/04 v33 9=>10 SavedAccountに項目追加
// 2017/5/08 v41 10=>11 MutedWord テーブルの追加
// 2017/5/17 v59 11=>12 PostDraft テーブルの追加
static DBOpenHelper db_open_helper;
@ -105,6 +107,7 @@ public class App1 extends Application {
AcctSet.onDBCreate( db );
AcctColor.onDBCreate( db );
MutedWord.onDBCreate( db );
PostDraft.onDBCreate( db );
}
@Override
@ -121,6 +124,7 @@ public class App1 extends Application {
AcctSet.onDBUpgrade( db, oldVersion, newVersion );
AcctColor.onDBUpgrade( db, oldVersion, newVersion );
MutedWord.onDBUpgrade( db, oldVersion, newVersion );
PostDraft.onDBUpgrade( db, oldVersion, newVersion );
}
}

View File

@ -0,0 +1,146 @@
package jp.juggler.subwaytooter.table;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.NonNull;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import jp.juggler.subwaytooter.App1;
import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.util.Utils;
public class PostDraft {
private static final LogCategory log = new LogCategory( "PostDraft" );
private static final String table = "post_draft";
private static final String COL_TIME_SAVE = "time_save";
private static final String COL_JSON = "json";
private static final String COL_HASH = "hash";
public static void onDBCreate( SQLiteDatabase db ){
log.d( "onDBCreate!" );
db.execSQL(
"create table if not exists " + table
+ "(_id INTEGER PRIMARY KEY"
+ "," + COL_TIME_SAVE + " integer not null"
+ "," + COL_JSON + " text not null"
+ "," + COL_HASH + " text not null"
+ ")"
);
db.execSQL(
"create unique index if not exists " + table + "_hash on " + table + "(" + COL_HASH + ")"
);
db.execSQL(
"create index if not exists " + table + "_time on " + table + "(" + COL_TIME_SAVE + ")"
);
}
public static void onDBUpgrade( SQLiteDatabase db, int oldVersion, int newVersion ){
if( oldVersion < 12 && newVersion >= 12 ){
onDBCreate( db );
}
}
public static void deleteOld( long now ){
try{
// 古いデータを掃除する
long expire = now - 86400000L * 30;
App1.getDB().delete( table, COL_TIME_SAVE + "<?", new String[]{ Long.toString( expire ) } );
}catch( Throwable ex ){
log.e( ex, "deleteOld failed." );
}
}
public static void save( long now, @NonNull JSONObject json ){
deleteOld(now);
try{
// make hash
StringBuilder sb = new StringBuilder( );
ArrayList<String> keys = new ArrayList<>( );
for( Iterator<String> it = json.keys(); it.hasNext() ;){
keys.add( it.next() );
}
Collections.sort( keys );
for( String k : keys ){
String v = json.isNull( k ) ? "(null)" : json.opt(k).toString();
sb.append( "&");
sb.append( k );
sb.append( "=");
sb.append( v );
}
String hash = Utils.digestSHA256( sb.toString() );
// save to db
ContentValues cv = new ContentValues();
cv.put( COL_TIME_SAVE, now );
cv.put( COL_JSON, json.toString() );
cv.put( COL_HASH, hash );
App1.getDB().replace( table, null, cv );
}catch( Throwable ex ){
log.e( ex, "save failed." );
}
}
// public static void saveList( long now, String[] src_list, int offset, int length ){
//
// try{
// ContentValues cv = new ContentValues();
// cv.put( COL_TIME_SAVE, now );
//
// boolean bOK = false;
// SQLiteDatabase db = App1.getDB();
// db.execSQL( "BEGIN TRANSACTION" );
// try{
// for( int i = 0 ; i < length ; ++ i ){
// String acct = src_list[ i + offset ];
// cv.put( COL_ACCT, acct );
// db.replace( table, null, cv );
// }
// bOK = true;
// }catch( Throwable ex ){
// ex.printStackTrace();
// log.e( ex, "saveList failed." );
// }
// if( bOK ){
// db.execSQL( "COMMIT TRANSACTION" );
// }else{
// db.execSQL( "ROLLBACK TRANSACTION" );
// }
// }catch( Throwable ex ){
// ex.printStackTrace();
// log.e( ex, "saveList failed." );
// }
// }
@NonNull public static ArrayList< JSONObject > loadList(int limit){
ArrayList< JSONObject > result = new ArrayList<>( );
try{
Cursor cursor = App1.getDB().query( table, null, null, null, null, null, COL_TIME_SAVE + " desc limit "+limit );
if( cursor != null ){
try{
int idx_json = cursor.getColumnIndex( COL_JSON );
while( cursor.moveToNext() ){
result.add( new JSONObject( cursor.getString( idx_json ) ));
}
}finally{
cursor.close();
}
}
}catch( Throwable ex ){
ex.printStackTrace();
log.e( ex, "searchPrefix failed." );
}
return result;
}
}

View File

@ -22,10 +22,10 @@
>
<LinearLayout
android:id="@+id/llContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="@+id/llContent"
android:paddingBottom="320dp"
android:paddingEnd="12dp"
android:paddingStart="12dp"
@ -219,15 +219,14 @@
</ScrollView>
<LinearLayout
android:id="@+id/llFooterBar"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="?attr/color_column_header"
android:baselineAligned="false"
android:orientation="horizontal"
android:id="@+id/llFooterBar"
>
<ImageButton
android:id="@+id/btnAttachment"
android:layout_width="48dp"
@ -249,19 +248,14 @@
tools:src="?attr/ic_public"
/>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"
/>
<ImageButton
android:id="@+id/btnClear"
android:id="@+id/btnMore"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="4dp"
android:background="@drawable/btn_bg_transparent"
android:contentDescription="@string/clear"
android:src="?attr/ic_delete"
android:contentDescription="@string/more"
android:src="?attr/btn_more"
/>
<View
@ -270,6 +264,7 @@
android:layout_weight="1"
/>
<TextView
android:id="@+id/tvCharCount"
android:layout_width="wrap_content"

View File

@ -317,6 +317,11 @@
<string name="launcher_icon_by">Icone de l\'application créée par フタバ</string>
<string name="ssl_bug_7_0">Android 7.0 ne peut utiliser qu\'elliptic curve \"prime256v1\" pour les connexions sécurisées.\nL\'instance à laquelle vous voulez vous connecter semble ne pas le gérer.\n-Vous pouvez mettre à jour le système d\'exploitation vers Android 7.1.1.\n-Ou, demander au propriétaire de l\'instance de prendre en charge elliptic curve \"prime256v1\".\nPlus de détails ici: https://code.google.com/p/android/issues/detail?id=224438</string>
<string name="timeline_font">Changer la police de caractères de l\'interface\n(redémarrage nécessaire)</string>
<string name="account_in_draft_is_lost">The account used for this draft has been removed from app. The following context can not be restored. in_reply_to, media attachment.</string>
<string name="attachment_in_draft_is_lost">Some of media attachment in the draft have been lost.</string>
<string name="reply_to_in_draft_is_lost">The reply origin referred by draft has been lost. The following context is removed. in_reply_to</string>
<string name="restore_draft">Restore from draft</string>
<string name="select_draft">Which draft to restore ?</string>
<!--<string name="abc_action_bar_home_description">Revenir à l\'accueil</string>-->

View File

@ -605,4 +605,9 @@
<string name="ssl_bug_7_0">Android 7.0 には利用できる elliptic curve が \"prime256v1\" だけというバグがあります。\nしかし対象のインスタンスはこの elliptic curve に対応していないようです。\n可能ならOSバージョン 7.1.1以降にアップデートするとバグは修正されます。\nもしくは elliptic curve \"prime256v1\" に対応してもらうよう、あなたからインスタンスのオーナーに要望を出すことができます。\n関連情報 https://code.google.com/p/android/issues/detail?id=224438</string>
<string name="timeline_font">タイムラインのフォント(アプリ再起動が必要)</string>
<string name="account_in_draft_is_lost">下書きで使われているアカウント情報がありません。以下の文脈は失われます。in_reply_to, 添付メディア</string>
<string name="attachment_in_draft_is_lost">下書き中の添付メディアのいくつかは失われました</string>
<string name="reply_to_in_draft_is_lost">下書きから参照されている返信元の投稿は失われました。以下の文脈を再現できません。in_reply_to</string>
<string name="restore_draft">下書きから復元</string>
<string name="select_draft">どの下書きから復元しますか?</string>
</resources>

View File

@ -314,4 +314,9 @@
<string name="launcher_icon_by">Thanks to フタバ for the application icon.</string>
<string name="ssl_bug_7_0">Android 7.0 can only use the elliptic curve \"prime256v1\".\nUnfortunately your instance seems to not support it.\nThis bug has been fixed in Android 7.1.1.\nYou can either upgrade your OS or ask the administrator of your instance to add support to the elliptic curve \"prime256v1\".\nSee also https://code.google.com/p/android/issues/detail?id=224438</string>
<string name="timeline_font">Timeline font (app restart required)</string>
<string name="restore_draft">Restore from draft</string>
<string name="select_draft">Which draft to restore ?</string>
<string name="account_in_draft_is_lost">The account used for this draft has been removed from app. The following context can not be restored. in_reply_to, media attachment.</string>
<string name="reply_to_in_draft_is_lost">The reply origin referred by draft has been lost. The following context is removed. in_reply_to</string>
<string name="attachment_in_draft_is_lost">Some of media attachment in the draft have been lost.</string>
</resources>