From 16ac5c68805ac6196c00a1540a6b8d907db388bc Mon Sep 17 00:00:00 2001 From: tateisu Date: Tue, 22 Aug 2017 03:10:12 +0900 Subject: [PATCH] =?UTF-8?q?v1.1.9=20-=20=E3=83=AA=E3=83=A2=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=83=A6=E3=83=BC=E3=82=B6=E3=81=AE=E3=83=97=E3=83=AD?= =?UTF-8?q?=E3=83=95=E3=82=A3=E3=83=BC=E3=83=AB=E3=81=AB=E8=AD=A6=E5=91=8A?= =?UTF-8?q?=E3=82=92=E8=A1=A8=E7=A4=BA=20-=20=E3=83=88=E3=82=A5=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=81=AE=E5=85=AC=E9=96=8B=E7=AF=84=E5=9B=B2=E3=82=92?= =?UTF-8?q?TL=E4=B8=AD=E3=81=AB=E8=A1=A8=E7=A4=BA=E3=81=99=E3=82=8B=20-=20?= =?UTF-8?q?=E3=82=A2=E3=82=AB=E3=82=A6=E3=83=B3=E3=83=88=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E3=81=AB=E3=83=97=E3=83=AD=E3=83=95=E3=82=A3=E3=83=BC=E3=83=AB?= =?UTF-8?q?=E7=B7=A8=E9=9B=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 4 +- .../subwaytooter/ActAccountSetting.java | 600 +++++++++++++++++- .../java/jp/juggler/subwaytooter/Column.java | 8 +- .../subwaytooter/HeaderViewHolderProfile.java | 11 +- .../juggler/subwaytooter/ItemViewHolder.java | 19 +- .../subwaytooter/table/SavedAccount.java | 19 + .../subwaytooter/util/EmojiImageSpan.java | 4 +- .../subwaytooter/view/MyNetworkImageView.java | 2 + .../main/res/drawable-hdpi/ic_question.png | Bin 0 -> 1151 bytes .../res/drawable-hdpi/ic_question_dark.png | Bin 0 -> 1033 bytes .../main/res/drawable-mdpi/ic_question.png | Bin 0 -> 732 bytes .../res/drawable-mdpi/ic_question_dark.png | Bin 0 -> 648 bytes .../main/res/drawable-xhdpi/ic_question.png | Bin 0 -> 1486 bytes .../res/drawable-xhdpi/ic_question_dark.png | Bin 0 -> 1306 bytes .../main/res/drawable-xxhdpi/ic_question.png | Bin 0 -> 2444 bytes .../res/drawable-xxhdpi/ic_question_dark.png | Bin 0 -> 2244 bytes .../main/res/layout/act_account_setting.xml | 125 +++- app/src/main/res/layout/lv_list_header.xml | 10 +- app/src/main/res/values-fr/strings.xml | 8 +- app/src/main/res/values-ja/strings.xml | 6 + app/src/main/res/values/attrs.xml | 1 + app/src/main/res/values/strings.xml | 6 + app/src/main/res/values/styles.xml | 2 + 23 files changed, 785 insertions(+), 40 deletions(-) create mode 100644 app/src/main/res/drawable-hdpi/ic_question.png create mode 100644 app/src/main/res/drawable-hdpi/ic_question_dark.png create mode 100644 app/src/main/res/drawable-mdpi/ic_question.png create mode 100644 app/src/main/res/drawable-mdpi/ic_question_dark.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_question.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_question_dark.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_question.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_question_dark.png diff --git a/app/build.gradle b/app/build.gradle index 8eb41314..0e562772 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "jp.juggler.subwaytooter" minSdkVersion 21 targetSdkVersion 25 - versionCode 118 - versionName "1.1.8" + versionCode 119 + versionName "1.1.9" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.java b/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.java index f69131fb..658b0a0b 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.java @@ -1,34 +1,58 @@ package jp.juggler.subwaytooter; +import android.Manifest; import android.app.Activity; -import android.app.Dialog; import android.app.ProgressDialog; +import android.content.ClipData; +import android.content.ContentValues; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; import android.media.RingtoneManager; import android.net.Uri; import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; +import android.provider.MediaStore; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.text.TextUtils; +import android.util.Base64; +import android.util.Base64OutputStream; import android.view.View; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; +import android.widget.EditText; import android.widget.Switch; import android.widget.TextView; +import org.apache.commons.io.IOUtils; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + import jp.juggler.subwaytooter.api.TootApiClient; import jp.juggler.subwaytooter.api.TootApiResult; +import jp.juggler.subwaytooter.api.entity.TootAccount; import jp.juggler.subwaytooter.api.entity.TootStatus; -import jp.juggler.subwaytooter.dialog.DlgAccessToken; +import jp.juggler.subwaytooter.dialog.ActionsDialog; import jp.juggler.subwaytooter.table.AcctColor; import jp.juggler.subwaytooter.table.SavedAccount; +import jp.juggler.subwaytooter.util.Emojione; import jp.juggler.subwaytooter.util.LogCategory; import jp.juggler.subwaytooter.util.Utils; +import jp.juggler.subwaytooter.view.MyNetworkImageView; import okhttp3.Call; import okhttp3.Request; import okhttp3.RequestBody; @@ -50,16 +74,22 @@ public class ActAccountSetting extends AppCompatActivity } SavedAccount account; + SharedPreferences pref; @Override protected void onCreate( @Nullable Bundle savedInstanceState ){ super.onCreate( savedInstanceState ); + App1.setActivityTheme( this, false ); + this.pref = App1.pref; + initUI(); account = SavedAccount.loadAccount( this, log, getIntent().getLongExtra( KEY_ACCOUNT_DB_ID, - 1L ) ); if( account == null ) finish(); loadUIFromData( account ); + initializeProfile(); + btnOpenBrowser.setText( getString( R.string.open_instance_website, account.host ) ); } @@ -70,26 +100,89 @@ public class ActAccountSetting extends AppCompatActivity static final int REQUEST_CODE_ACCT_CUSTOMIZE = 1; static final int REQUEST_CODE_NOTIFICATION_SOUND = 2; + private static final int REQUEST_CODE_AVATAR_ATTACHMENT = 3; + private static final int REQUEST_CODE_HEADER_ATTACHMENT = 4; + private static final int REQUEST_CODE_AVATAR_CAMERA = 5; + private static final int REQUEST_CODE_HEADER_CAMERA = 6; @Override protected void onActivityResult( int requestCode, int resultCode, Intent data ){ - if( requestCode == REQUEST_CODE_ACCT_CUSTOMIZE && resultCode == RESULT_OK ){ - showAcctColor(); - }else if( resultCode == RESULT_OK && requestCode == REQUEST_CODE_NOTIFICATION_SOUND ){ - // RINGTONE_PICKERからの選択されたデータを取得する - Uri uri = (Uri) data.getExtras().get( RingtoneManager.EXTRA_RINGTONE_PICKED_URI ); - if( uri != null ){ - notification_sound_uri = uri.toString(); - saveUIToData(); - // Ringtone ringtone = RingtoneManager.getRingtone(getApplicationContext(), uri); - // TextView ringView = (TextView) findViewById(R.id.ringtone); - // ringView.setText(ringtone.getTitle(getApplicationContext())); - // ringtone.setStreamType(AudioManager.STREAM_ALARM); - // ringtone.play(); - // SystemClock.sleep(1000); - // ringtone.stop(); + switch( requestCode ){ + default: + super.onActivityResult( requestCode, resultCode, data ); + break; + case REQUEST_CODE_ACCT_CUSTOMIZE:{ + if( resultCode == RESULT_OK ){ + showAcctColor(); } + break; + } + case REQUEST_CODE_NOTIFICATION_SOUND:{ + if( resultCode == RESULT_OK ){ + // RINGTONE_PICKERからの選択されたデータを取得する + Uri uri = (Uri) data.getExtras().get( RingtoneManager.EXTRA_RINGTONE_PICKED_URI ); + if( uri != null ){ + notification_sound_uri = uri.toString(); + saveUIToData(); + // Ringtone ringtone = RingtoneManager.getRingtone(getApplicationContext(), uri); + // TextView ringView = (TextView) findViewById(R.id.ringtone); + // ringView.setText(ringtone.getTitle(getApplicationContext())); + // ringtone.setStreamType(AudioManager.STREAM_ALARM); + // ringtone.play(); + // SystemClock.sleep(1000); + // ringtone.stop(); + } + } + break; + } + case REQUEST_CODE_AVATAR_ATTACHMENT: + case REQUEST_CODE_HEADER_ATTACHMENT:{ + + if( resultCode == RESULT_OK && data != null ){ + Uri uri = data.getData(); + if( uri != null ){ + // 単一選択 + String type = data.getType(); + if( TextUtils.isEmpty( type ) ){ + type = getContentResolver().getType( uri ); + } + addAttachment( requestCode, uri, type ); + break; + } + ClipData cd = data.getClipData(); + if( cd != null ){ + int count = cd.getItemCount(); + if( count > 0 ){ + ClipData.Item item = cd.getItemAt( 0 ); + uri = item.getUri(); + String type = getContentResolver().getType( uri ); + addAttachment( requestCode, uri, type ); + } + } + } + break; + } + case REQUEST_CODE_AVATAR_CAMERA: + case REQUEST_CODE_HEADER_CAMERA:{ + + if( resultCode != RESULT_OK ){ + // 失敗したら DBからデータを削除 + if( uriCameraImage != null ){ + getContentResolver().delete( uriCameraImage, null, null ); + uriCameraImage = null; + } + }else{ + // 画像のURL + Uri uri = ( data == null ? null : data.getData() ); + if( uri == null ) uri = uriCameraImage; + + if( uri != null ){ + String type = getContentResolver().getType( uri ); + addAttachment( requestCode, uri, type ); + } + } + break; + } } - super.onActivityResult( requestCode, resultCode, data ); } TextView tvInstance; @@ -120,6 +213,15 @@ public class ActAccountSetting extends AppCompatActivity String notification_sound_uri; + MyNetworkImageView ivProfileHeader; + MyNetworkImageView ivProfileAvatar; + View btnProfileAvatar; + View btnProfileHeader; + EditText etDisplayName; + View btnDisplayName; + EditText etNote; + View btnNote; + private void initUI(){ setContentView( R.layout.act_account_setting ); @@ -147,12 +249,25 @@ public class ActAccountSetting extends AppCompatActivity tvUserCustom = (TextView) findViewById( R.id.tvUserCustom ); btnUserCustom = findViewById( R.id.btnUserCustom ); + ivProfileHeader = (MyNetworkImageView) findViewById( R.id.ivProfileHeader ); + ivProfileAvatar = (MyNetworkImageView) findViewById( R.id.ivProfileAvatar ); + btnProfileAvatar = findViewById( R.id.btnProfileAvatar ); + btnProfileHeader = findViewById( R.id.btnProfileHeader ); + etDisplayName = (EditText) findViewById( R.id.etDisplayName ); + btnDisplayName = findViewById( R.id.btnDisplayName ); + etNote = (EditText) findViewById( R.id.etNote ); + btnNote = findViewById( R.id.btnNote ); + btnOpenBrowser.setOnClickListener( this ); btnAccessToken.setOnClickListener( this ); btnInputAccessToken.setOnClickListener( this ); btnAccountRemove.setOnClickListener( this ); btnVisibility.setOnClickListener( this ); btnUserCustom.setOnClickListener( this ); + btnProfileAvatar.setOnClickListener( this ); + btnProfileHeader.setOnClickListener( this ); + btnDisplayName.setOnClickListener( this ); + btnNote.setOnClickListener( this ); swNSFWOpen.setOnCheckedChangeListener( this ); cbNotificationMention.setOnCheckedChangeListener( this ); @@ -290,6 +405,23 @@ public class ActAccountSetting extends AppCompatActivity notification_sound_uri = ""; saveUIToData(); break; + + case R.id.btnProfileAvatar: + pickAvatarImage(); + break; + + case R.id.btnProfileHeader: + pickHeaderImage(); + break; + + case R.id.btnDisplayName: + sendDisplayName(); + break; + + case R.id.btnNote: + sendNote(); + break; + } } @@ -518,5 +650,437 @@ public class ActAccountSetting extends AppCompatActivity startActivityForResult( chooser, REQUEST_CODE_NOTIFICATION_SOUND ); } + ////////////////////////////////////////////////////////////////////////// + + private void initializeProfile(){ + // 初期状態 + ivProfileAvatar.setErrorImageResId( Styler.getAttributeResourceId( this, R.attr.ic_question ) ); + ivProfileAvatar.setDefaultImageResId( Styler.getAttributeResourceId( this, R.attr.ic_question ) ); + etDisplayName.setText( "(loading...)" ); + etNote.setText( "(loading...)" ); + // 初期状態では編集不可能 + btnProfileAvatar.setEnabled( false ); + btnProfileHeader.setEnabled( false ); + etDisplayName.setEnabled( false ); + btnDisplayName.setEnabled( false ); + etNote.setEnabled( false ); + btnNote.setEnabled( false ); + // 疑似アカウントなら編集不可のまま + if( account.isPseudo() ) return; + + loadProfile(); + } + + void loadProfile(){ + // サーバから情報をロードする + + final ProgressDialog progress = new ProgressDialog( this ); + + final AsyncTask< Void, Void, TootApiResult > task = new AsyncTask< Void, Void, TootApiResult >() { + + TootAccount data; + + @Override protected TootApiResult doInBackground( Void... params ){ + TootApiClient client = new TootApiClient( ActAccountSetting.this, new TootApiClient.Callback() { + @Override public boolean isApiCancelled(){ + return isCancelled(); + } + + @Override public void publishApiProgress( final String s ){ + } + } ); + client.setAccount( account ); + + TootApiResult result = client.request( "/api/v1/accounts/verify_credentials" ); + if( result != null && result.object != null ){ + data = TootAccount.parse( ActAccountSetting.this, account, result.object ); + if( data == null ) return new TootApiResult( "TootAccount parse failed." ); + } + return result; + } + + @Override + protected void onCancelled( TootApiResult result ){ + super.onPostExecute( result ); + } + + @Override + protected void onPostExecute( TootApiResult result ){ + try{ + progress.dismiss(); + }catch( Throwable ignored ){ + } + if( result == null ){ + // cancelled. + }else if( data != null ){ + showProfile( data ); + }else{ + Utils.showToast( ActAccountSetting.this, true, result.error ); + } + } + + }; + task.executeOnExecutor( App1.task_executor ); + progress.setIndeterminate( true ); + progress.setOnDismissListener( new DialogInterface.OnDismissListener() { + @Override public void onDismiss( DialogInterface dialog ){ + task.cancel( true ); + } + } ); + progress.show(); + } + + void showProfile( TootAccount src ){ + ivProfileAvatar.setImageUrl( App1.pref, 16f, src.avatar_static, src.avatar ); + ivProfileHeader.setImageUrl( App1.pref, 0f, src.header_static, src.header ); + + etDisplayName.setText( Emojione.decodeEmoji( this, src.display_name == null ? "" : src.display_name ) ); + + String note; + if( src.source != null && src.source.note != null ){ + note = src.source.note; + }else{ + note = src.note; + } + etNote.setText( Emojione.decodeEmoji( this, note == null ? "" : note ) ); + + // 編集可能にする + btnProfileAvatar.setEnabled( true ); + btnProfileHeader.setEnabled( true ); + etDisplayName.setEnabled( true ); + btnDisplayName.setEnabled( true ); + etNote.setEnabled( true ); + btnNote.setEnabled( true ); + } + + void updateCredential( final String form_data ){ + final ProgressDialog progress = new ProgressDialog( this ); + + final AsyncTask< Void, Void, TootApiResult > task = new AsyncTask< Void, Void, TootApiResult >() { + + TootAccount data; + + @Override protected TootApiResult doInBackground( Void... params ){ + TootApiClient client = new TootApiClient( ActAccountSetting.this, new TootApiClient.Callback() { + @Override public boolean isApiCancelled(){ + return isCancelled(); + } + + @Override public void publishApiProgress( final String s ){ + } + } ); + client.setAccount( account ); + + Request.Builder request_builder = new Request.Builder() + .patch( RequestBody.create( + TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED + , form_data + ) ); + + TootApiResult result = client.request( "/api/v1/accounts/update_credentials", request_builder ); + if( result != null && result.object != null ){ + data = TootAccount.parse( ActAccountSetting.this, account, result.object ); + if( data == null ) return new TootApiResult( "TootAccount parse failed." ); + } + return result; + } + + @Override + protected void onCancelled( TootApiResult result ){ + super.onPostExecute( result ); + } + + @Override + protected void onPostExecute( TootApiResult result ){ + try{ + progress.dismiss(); + }catch( Throwable ignored ){ + } + loadProfile(); + if( result == null ){ + // cancelled. + }else if( data != null ){ + showProfile( data ); + }else{ + Utils.showToast( ActAccountSetting.this, true, result.error ); + } + } + + }; + task.executeOnExecutor( App1.task_executor ); + progress.setIndeterminate( true ); + progress.setOnDismissListener( new DialogInterface.OnDismissListener() { + @Override public void onDismiss( DialogInterface dialog ){ + task.cancel( true ); + } + } ); + progress.show(); + } + + private void sendDisplayName(){ + updateCredential( "display_name=" + Uri.encode( etDisplayName.getText().toString() ) ); + } + + private void sendNote(){ + updateCredential( "note=" + Uri.encode( etNote.getText().toString() ) ); + } + + private static final int PERMISSION_REQUEST_AVATAR = 1; + private static final int PERMISSION_REQUEST_HEADER = 2; + + private void pickAvatarImage(){ + openPicker( PERMISSION_REQUEST_AVATAR ); + } + + private void pickHeaderImage(){ + openPicker( PERMISSION_REQUEST_HEADER ); + } + + void openPicker( final int permission_request_code ){ + int permissionCheck = ContextCompat.checkSelfPermission( this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE ); + if( permissionCheck != PackageManager.PERMISSION_GRANTED ){ + preparePermission( permission_request_code ); + return; + } + + ActionsDialog a = new ActionsDialog(); + a.addAction( getString( R.string.image_pick ), new Runnable() { + @Override public void run(){ + performAttachment( permission_request_code == PERMISSION_REQUEST_AVATAR ? REQUEST_CODE_AVATAR_ATTACHMENT : REQUEST_CODE_HEADER_ATTACHMENT ); + } + } ); + a.addAction( getString( R.string.image_capture ), new Runnable() { + @Override public void run(){ + performCamera( permission_request_code == PERMISSION_REQUEST_AVATAR ? REQUEST_CODE_AVATAR_CAMERA : REQUEST_CODE_HEADER_CAMERA ); + } + } ); + a.show( this, null ); + } + + private void preparePermission( int request_code ){ + if( Build.VERSION.SDK_INT >= 23 ){ + // No explanation needed, we can request the permission. + + ActivityCompat.requestPermissions( this + , new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE } + , request_code + ); + return; + } + Utils.showToast( this, true, R.string.missing_storage_permission ); + } + + @Override public void onRequestPermissionsResult( + int requestCode + , @NonNull String permissions[] + , @NonNull int[] grantResults + ){ + switch( requestCode ){ + case PERMISSION_REQUEST_AVATAR: + case PERMISSION_REQUEST_HEADER: + // If request is cancelled, the result arrays are empty. + if( grantResults.length > 0 && + grantResults[ 0 ] == PackageManager.PERMISSION_GRANTED + ){ + openPicker( requestCode ); + }else{ + Utils.showToast( this, true, R.string.missing_storage_permission ); + } + break; + } + } + + private void performAttachment( final int request_code ){ + // SAFのIntentで開く + try{ + Intent intent = new Intent( Intent.ACTION_OPEN_DOCUMENT ); + intent.addCategory( Intent.CATEGORY_OPENABLE ); + intent.setType( "*/*" ); + intent.putExtra( Intent.EXTRA_ALLOW_MULTIPLE, false ); + intent.putExtra( Intent.EXTRA_MIME_TYPES, new String[]{ "image/*", "video/*" } ); + startActivityForResult( intent + , request_code ); + }catch( Throwable ex ){ + log.trace( ex ); + Utils.showToast( this, ex, "ACTION_OPEN_DOCUMENT failed." ); + } + } + + Uri uriCameraImage; + + private void performCamera( final int request_code ){ + + try{ + // カメラで撮影 + String filename = System.currentTimeMillis() + ".jpg"; + ContentValues values = new ContentValues(); + values.put( MediaStore.Images.Media.TITLE, filename ); + values.put( MediaStore.Images.Media.MIME_TYPE, "image/jpeg" ); + uriCameraImage = getContentResolver().insert( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values ); + + Intent intent = new Intent( MediaStore.ACTION_IMAGE_CAPTURE ); + intent.putExtra( MediaStore.EXTRA_OUTPUT, uriCameraImage ); + + startActivityForResult( intent, request_code ); + }catch( Throwable ex ){ + log.trace( ex ); + Utils.showToast( this, ex, "opening camera app failed." ); + } + } + + interface InputStreamOpener { + InputStream open() throws IOException; + + String getMimeType(); + + void deleteTempFile(); + } + + static final String MIME_TYPE_JPEG = "image/jpeg"; + static final String MIME_TYPE_PNG = "image/png"; + + private InputStreamOpener createOpener( final Uri uri, final String mime_type ){ + //noinspection LoopStatementThatDoesntLoop + for( ; ; ){ + try{ + + // 画像の種別 + boolean is_jpeg = MIME_TYPE_JPEG.equals( mime_type ); + boolean is_png = MIME_TYPE_PNG.equals( mime_type ); + if( ! is_jpeg && ! is_png ){ + log.d( "createOpener: source is not jpeg or png" ); + break; + } + + // 設定からリサイズ指定を読む + int resize_to = 1280; + + Bitmap bitmap = Utils.createResizedBitmap( log, this, uri, false, resize_to ); + if( bitmap != null ){ + try{ + File cache_dir = getExternalCacheDir(); + if( cache_dir == null ){ + Utils.showToast( this, false, "getExternalCacheDir returns null." ); + break; + } + + //noinspection ResultOfMethodCallIgnored + cache_dir.mkdir(); + + final File temp_file = new File( cache_dir, "tmp." + Thread.currentThread().getId() ); + FileOutputStream os = new FileOutputStream( temp_file ); + try{ + if( is_jpeg ){ + bitmap.compress( Bitmap.CompressFormat.JPEG, 95, os ); + }else{ + bitmap.compress( Bitmap.CompressFormat.PNG, 100, os ); + } + }finally{ + os.close(); + } + + return new InputStreamOpener() { + @Override public InputStream open() throws IOException{ + return new FileInputStream( temp_file ); + } + + @Override public String getMimeType(){ + return mime_type; + } + + @Override public void deleteTempFile(){ + //noinspection ResultOfMethodCallIgnored + temp_file.delete(); + } + }; + }finally{ + bitmap.recycle(); + } + } + + }catch( Throwable ex ){ + log.trace( ex ); + Utils.showToast( this, ex, "Resizing image failed." ); + } + + break; + } + return new InputStreamOpener() { + @Override public InputStream open() throws IOException{ + return getContentResolver().openInputStream( uri ); + } + + @Override public String getMimeType(){ + return mime_type; + } + + @Override public void deleteTempFile(){ + + } + }; + } + + void addAttachment( final int request_code, final Uri uri, final String mime_type ){ + + if( mime_type == null ){ + Utils.showToast( this, false, "mime type is not provided." ); + return; + } + + if( ! mime_type.startsWith( "image/" ) ){ + Utils.showToast( this, false, "mime type is not image." ); + return; + } + + new AsyncTask< Void, Void, String >() { + + @Override protected String doInBackground( Void... params ){ + try{ + final InputStreamOpener opener = createOpener( uri, mime_type ); + try{ + InputStream is = opener.open(); + try{ + ByteArrayOutputStream bao = new ByteArrayOutputStream(); + // + bao.write( Utils.encodeUTF8( "data:" + opener.getMimeType() + ";base64," ) ); + // + Base64OutputStream base64 = new Base64OutputStream( bao, Base64.NO_WRAP ); + try{ + IOUtils.copy( is, base64 ); + }finally{ + base64.close(); + } + String value = Utils.decodeUTF8( bao.toByteArray() ); + + switch( request_code ){ + case REQUEST_CODE_AVATAR_ATTACHMENT: + case REQUEST_CODE_AVATAR_CAMERA: + return "avatar=" + Uri.encode( value ); + case REQUEST_CODE_HEADER_ATTACHMENT: + case REQUEST_CODE_HEADER_CAMERA: + return "header=" + Uri.encode( value ); + } + }finally{ + IOUtils.closeQuietly( is ); + } + }finally{ + opener.deleteTempFile(); + } + + }catch( Throwable ex ){ + Utils.showToast( ActAccountSetting.this, ex, "image converting failed." ); + } + return null; + } + + @Override + protected void onPostExecute( String form_data ){ + if( form_data != null ){ + updateCredential( form_data ); + } + } + + }.executeOnExecutor( App1.task_executor ); + } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/Column.java b/app/src/main/java/jp/juggler/subwaytooter/Column.java index b8cd6ab3..198bce7f 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/Column.java +++ b/app/src/main/java/jp/juggler/subwaytooter/Column.java @@ -1323,10 +1323,10 @@ class Column implements StreamReader.Callback { return getStatuses( client, PATH_FEDERATE ); case TYPE_PROFILE: - if( who_account == null ){ - parseAccount1( client, String.format( Locale.JAPAN, PATH_ACCOUNT, profile_id ) ); - client.callback.publishApiProgress( "" ); - } + + parseAccount1( client, String.format( Locale.JAPAN, PATH_ACCOUNT, profile_id ) ); + client.callback.publishApiProgress( "" ); + switch( profile_tab ){ default: diff --git a/app/src/main/java/jp/juggler/subwaytooter/HeaderViewHolderProfile.java b/app/src/main/java/jp/juggler/subwaytooter/HeaderViewHolderProfile.java index db84dd90..291b2389 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/HeaderViewHolderProfile.java +++ b/app/src/main/java/jp/juggler/subwaytooter/HeaderViewHolderProfile.java @@ -28,6 +28,7 @@ class HeaderViewHolderProfile extends HeaderViewHolderBase implements View.OnCli private final ImageButton btnFollow; private final ImageView ivFollowedBy; private final View llProfile; + private final TextView tvRemoteProfileWarning; private TootAccount who; @@ -49,13 +50,15 @@ class HeaderViewHolderProfile extends HeaderViewHolderBase implements View.OnCli View btnMore = viewRoot.findViewById( R.id.btnMore ); btnFollow = (ImageButton) viewRoot.findViewById( R.id.btnFollow ); ivFollowedBy = (ImageView) viewRoot.findViewById( R.id.ivFollowedBy ); - + tvRemoteProfileWarning = (TextView) viewRoot.findViewById( R.id.tvRemoteProfileWarning ); + ivBackground.setOnClickListener( this ); btnFollowing.setOnClickListener( this ); btnFollowers.setOnClickListener( this ); btnStatusCount.setOnClickListener( this ); btnMore.setOnClickListener( this ); btnFollow.setOnClickListener( this ); + tvRemoteProfileWarning.setOnClickListener( this ); btnFollow.setOnLongClickListener( this ); @@ -77,6 +80,7 @@ class HeaderViewHolderProfile extends HeaderViewHolderBase implements View.OnCli showColor(); + if( who == null ){ tvCreated.setText( "" ); ivBackground.setImageDrawable( null ); @@ -89,11 +93,14 @@ class HeaderViewHolderProfile extends HeaderViewHolderBase implements View.OnCli btnFollowers.setText( activity.getString( R.string.followers ) + "\n" + "?" ); btnFollow.setImageDrawable( null ); + tvRemoteProfileWarning.setVisibility( View.GONE ); }else{ tvCreated.setText( TootStatus.formatTime( who.time_created_at ) ); ivBackground.setImageUrl( activity.pref, 0f,access_info.supplyBaseUrl( who.header_static ) ); ivAvatar.setImageUrl( activity.pref, 16f,access_info.supplyBaseUrl( who.avatar_static ) , access_info.supplyBaseUrl( who.avatar )); tvDisplayName.setText( who.decoded_display_name ); + + tvRemoteProfileWarning.setVisibility( column.access_info.isRemoteUser(who) ? View.VISIBLE : View.GONE ); String s = "@" + access_info.getFullAcct( who ); if( who.locked ){ @@ -117,6 +124,7 @@ class HeaderViewHolderProfile extends HeaderViewHolderBase implements View.OnCli switch( v.getId() ){ case R.id.ivBackground: + case R.id.tvRemoteProfileWarning: if( who != null ){ // 強制的にブラウザで開く activity.openChromeTab( activity.nextPosition( column ), access_info, who.url, true ); @@ -152,6 +160,7 @@ class HeaderViewHolderProfile extends HeaderViewHolderBase implements View.OnCli new DlgContextMenu( activity, column, who, null, null ).show(); } break; + } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.java b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.java index dac3584d..5bf0a704 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.java @@ -6,6 +6,8 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.view.ViewCompat; import android.support.v7.app.AlertDialog; +import android.text.SpannableStringBuilder; +import android.text.Spanned; import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; @@ -27,6 +29,7 @@ import jp.juggler.subwaytooter.table.ContentWarning; import jp.juggler.subwaytooter.table.MediaShown; import jp.juggler.subwaytooter.table.SavedAccount; import jp.juggler.subwaytooter.table.UserRelation; +import jp.juggler.subwaytooter.util.EmojiImageSpan; import jp.juggler.subwaytooter.util.LogCategory; import jp.juggler.subwaytooter.view.MyLinkMovementMethod; import jp.juggler.subwaytooter.view.MyListView; @@ -377,7 +380,21 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener { this.status = status; llStatus.setVisibility( View.VISIBLE ); - tvTime.setText( TootStatus.formatTime( status.time_created_at ) ); + if( status instanceof MSPToot ){ + tvTime.setText( TootStatus.formatTime( status.time_created_at ) ); + }else if( status instanceof TootStatus ){ + TootStatus ts = (TootStatus) status; + int icon_id = Styler.getVisibilityIcon( activity, ts.visibility ); + + SpannableStringBuilder sb = new SpannableStringBuilder( ); + int start = sb.length(); + sb.append(ts.visibility); + int end = sb.length(); + sb.setSpan( new EmojiImageSpan( activity, icon_id ), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE ); + sb.append(' '); + sb.append(TootStatus.formatTime( status.time_created_at )); + tvTime.setText(sb); + } account_thumbnail = status.account; setAcct( tvAcct, access_info.getFullAcct( status.account ) ); diff --git a/app/src/main/java/jp/juggler/subwaytooter/table/SavedAccount.java b/app/src/main/java/jp/juggler/subwaytooter/table/SavedAccount.java index df94b6c4..11035ae9 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/table/SavedAccount.java +++ b/app/src/main/java/jp/juggler/subwaytooter/table/SavedAccount.java @@ -464,6 +464,24 @@ public class SavedAccount extends TootAccount implements LinkClickContext { return "?@?"; } + public boolean isLocalUser( @NonNull TootAccount who ){ + return isLocalUser(who.acct); + } + + public boolean isLocalUser( @NonNull String acct ){ + int delm = acct.indexOf( '@' ); + if( delm == -1 ) return true; + return host.equalsIgnoreCase( acct.substring( delm+1 ) ); + } + + public boolean isRemoteUser( @NonNull TootAccount who ){ + return ! isLocalUser(who); + } + + public boolean isRemoteUser( @NonNull String acct ){ + return ! isLocalUser(acct); + } + public String getUserUrl( @NonNull String who_acct ){ int p = who_acct.indexOf( '@' ); if( - 1 != p ){ @@ -581,4 +599,5 @@ public class SavedAccount extends TootAccount implements LinkClickContext { return true; } + } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/EmojiImageSpan.java b/app/src/main/java/jp/juggler/subwaytooter/util/EmojiImageSpan.java index d7590025..cf0f6c36 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/EmojiImageSpan.java +++ b/app/src/main/java/jp/juggler/subwaytooter/util/EmojiImageSpan.java @@ -15,7 +15,7 @@ import android.text.style.ReplacementSpan; import java.lang.ref.WeakReference; -class EmojiImageSpan extends ReplacementSpan { +public class EmojiImageSpan extends ReplacementSpan { static DynamicDrawableSpan x = null; @@ -28,7 +28,7 @@ class EmojiImageSpan extends ReplacementSpan { private final int res_id; private WeakReference< Drawable > mDrawableRef; - EmojiImageSpan( @NonNull Context context, int res_id ){ + public EmojiImageSpan( @NonNull Context context, int res_id ){ super(); this.context = context.getApplicationContext(); this.res_id = res_id; diff --git a/app/src/main/java/jp/juggler/subwaytooter/view/MyNetworkImageView.java b/app/src/main/java/jp/juggler/subwaytooter/view/MyNetworkImageView.java index f7b4ee1f..9c66e92d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/view/MyNetworkImageView.java +++ b/app/src/main/java/jp/juggler/subwaytooter/view/MyNetworkImageView.java @@ -53,6 +53,7 @@ public class MyNetworkImageView extends AppCompatImageView { public void setDefaultImageResId( int defaultImage ){ mDefaultImageId = defaultImage; + loadImageIfNecessary(); } // エラー時に表示するDrawableのリソースID @@ -60,6 +61,7 @@ public class MyNetworkImageView extends AppCompatImageView { public void setErrorImageResId( int errorImage ){ mErrorImageId = errorImage; + loadImageIfNecessary(); } // 角丸の半径。元画像の短辺に対する割合を指定するらしい diff --git a/app/src/main/res/drawable-hdpi/ic_question.png b/app/src/main/res/drawable-hdpi/ic_question.png new file mode 100644 index 0000000000000000000000000000000000000000..7df3f002aa2804d1e4dfde26e0c878ea9404f059 GIT binary patch literal 1151 zcmV-_1c3XAP)nghdK|Sdw+$TxzNPlBI-5ww59kV!B}nL0^ij%`EdRO;)Ra+w;I| zT#hrdGk34sLVMxRZSTyPbLO1$oEJMf+E4pw&8JeSoJgnBoyB4?&i9rVHlNR5Po+}# zK`$WqN+y%p1nrXW+XjAnpUr0ZZDeR@=vqrCl}sitLZAr<`33ablelLb`!6(I@PUDW zbGXH)IQ$@l;F7Aa(A41I;F+cf4>!m{@GTeNu$t%(lo1q|9<2qIR)@umLIKIDj#eVQvW^9?aM2f(qdMy9rWt zZp41VC3#i6&E}pMi{f(L)Va()nM7RzcsrO0qpH(Yxv(b$vSwK#n1J35YakQ87_>qB z^{Fx~d{a_*wx0D+MFn!OpV+mz9D}SYVg+Il^r5DQc;!;X>J+f-|A8h0KeyhZ z#f6A?^{f_)Du}sHW@rdM>r>-va?*;6zrGY&-wx^b&&1(xeH#?Er!sC_piUN2b0K|y zO(<|PgbOaX7L+#Z-Scf5C7Ll1Squ60;j)w<6uUIsacuk++=xcwRzs%1D_ctT+TK2>rfEB}S#b%*unMqkl${u@=L-Nq7|Y8t0RMkik6{=EC_Jl8 zIac8;wjnfN_S7`%tFG?k7__~bc9S&SFmnZ~%erAM1KSs!B-$`J=|+njXWF_oUnRPA zO4J~hdBO)}RMlIcB@H8&r6|hyBbJ~$S;p(E_vctU5lL9Z*4j+ItKyDA&x?{>juK}g zyLnS!OLQ^`Q)~83N{b6%8J}xPnkU3EPuS+59%DuhxMo5_Wg`zWHp>oKJ2?uz&IuqV z761INl?tg?#g3_dqC%ITuPfSCzm++6Vxw}bejdRSqksx+I$K@X_VZsqe*i}{QB6~7 R4QBuV002ovPDHLkV1k$u7i0hc literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_question_dark.png b/app/src/main/res/drawable-hdpi/ic_question_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..a65fc1898105a7364497e1592bf006b096347899 GIT binary patch literal 1033 zcmV+k1or!hP)wES2k z#mv;$)!O}^Yz8K{4Rg+(bM~qWdExi6duHy;IWxbRIX4ivd@i5Ld`?eKs{s*0b)_2{ z1hfMB0JDG$U2E4W4{w2&UO*rMt3MCL|q}}o=>9fp}BAtfnL`F6G28Ci*Z{xsI zreIBT?9GCaUodtl3=>rR)`f6t(y$Y!*dZI*++xCAn)NRPXRj&Rj}5kVM5DJyV7{P< z18q$3P*qgm_bR|DTYp!h`#xK{T%y?Virs4BzyK4P)~t;XOXLaIPF&TjzfRQnSNE)J4)30O(TJlgZ@-6a6xOJI}DZg)2rFo-BpgwmQ;FD(VgR+-0UKGUwl#LDLhxpyb?<+*4An_Tr zgXbRx?|0DCTE-W#Abc#8edW91aCqG%GZ+k}!IzU)$o+<6NhRYDSdhl?f1S!ha%6El7mhFcz@t4Kn`qCnY+fi?vZU^<*q<}2LzgIlxo32ob zvTTcpJ@@glq}46@x54?md?%uiG$yo&DWP}8+ls;PM>h=L4ahvQwn>P_vlE( O0000R@vK literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_question_dark.png b/app/src/main/res/drawable-mdpi/ic_question_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..fb6b7a707a4be837fb5d13d0fa152266c7a08610 GIT binary patch literal 648 zcmV;30(bq1P)t)(z3>dOIIlV0yfuvB{x|6pXU44 zv-^wjBKF@RBcag~d72>Mb>)jv$Qo@9J_SZp&z%F7Mpgk2r6Xv z3DQA>Ppja0&=J7C101U)l0jN8R0e;-JDkf zflC0r3$i)VFCt3`5?)Ps>Ld67>EGIdi+;)4grf9FW7y{5FhvemJanV7iyGYro8mq> zO-Gt^$u8RPyK7S%p;xgv-LzYD%`xm;826ZxGL7hvSGGmU_Q0Z-A-y?^UY_&{ewM43 z>Sb6Bn}FHMNylvQF~2pNQ9Im$tsE}Pen|G_a5ELOadIjN(-UdibV#>nsOF8}*|6O^ zqsq}3QS-)IlkQ%S`=ZEdYT2iMJ3_tUJjk)UDoMs7O`PQsc~3lA0$1cvGyK>8lH#&= i%nIf{{{N>n%=H5qNmwZyS~he50000 z%D?sdk|C+&oVhc1=I$=$PBOVSW$(;+pKs0_3Wa>+BOm$5NA{zqr|0cTrSjFlz`$6! zT%PLh?>~+Ab9g_?_rAWqNxWC_+{@qoA9(IC3{Qixn|N*x4h}wdnfxujxq(^3dx>kk zQkn+Q$3XmDSB4$t9azZ`tXXVZ#>dCUcYyH|Ao5W}#{V<9HeIDuD!tiO%!9ZJ=2}*a z3xfCy!Y;Rq*5^Hme}ZQn&t*I>bMhT?1@oz=FwVEk8it03J_W(64h%mvOisX$ch&3l z*R(Zjwc0kskWL8oAbxuV-#l|ze+A!s+*IZf-FJh?2MNTjfw=Fwy1L#?yU+0O@OxN? z!EZf52vcdWrZu-{2rOvofKV(|qHwGrOBtUm_voZ(=ZCpEAX#TBsFDTTnLB%db%Amn-g;`==*n~(-AFfuNMrk{7D zlxHyc&a`pE7wtJ_hlY|Giy_)SE)fMEJ>>B1ev5xT0AVwXA7-9mK(q*&X!W_>}@iaADKQUvy^3s_Qu?44BggprYveQC8%FrqMWHR&vX z^_Qho3mo#HgV2vC`w>&#Y2PjsS(g@&|WWJ8) zg=yj2>mGZr2m<)gFj{Z_Y9;`Wr-#CAoS5$xr( zLG(owpKq1`Oo?q>L5;tq!Sa#-cn#NfE?}t)w8sxD!W*VeRDP}(xvX42$JxL4VUckw3B;sOKT&aC2*VD>ePMQg{?~-Vz zyE!kyVuzxgY9#HCDeHe7A)QVHLt(SfYF3$m0ZA%`Myg&*Dez_*Eu9gx^f<;Hy@WG} z07KX1HJk*4*`(pUSZ{A{w_>Ifg*F|$iwySvmvH%-Ab@+JfhsE~tQ}la5s{gS36rin(O8yT4RU4tFNKLZSN-S7RYg#b{ zV1UmxMf)d$eTx#b-02-{wE%d|*BUfjuGU<0>$lp?Fno8kRRYiem6?^8LAC<~{;asd zGhZRZ5P-Q++Ef%XpGBG}YR!*^kfY708JhGwX}M5cjb{1+IQranhsVGek{uk+C`-0G zM|Q7essWalrPR1JMG_~(1WR1Y+%-J1HD?iDGzE1TAv{dD+rMYJLQ*yC~fKqwm8wj%NNs9e?cm22+ZGR_gbs)cTvj@HdAHcgEdA9 z#>}hcZw=4pi3N7AwfY>=L7d>y02Q%W`aT=C+UhiB8XTr=0Wt#P#QUe(`u`1>qQq3p z_IcB(a-2O&g;!+|u&}7Y%92Z0xxn---Uzc(r5PQl%}AX)S#36{k@pU>yn!<1u~}o< o?Ec)x{)9q4@{x~xFSxIQPbCf6ivR!s07*qoM6N<$f|2&hrT_o{ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_question_dark.png b/app/src/main/res/drawable-xhdpi/ic_question_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..73330f890b937f0532c01ba6ccb837de69a05eeb GIT binary patch literal 1306 zcmV+#1?BpQP)q}HY6kk`fGF{C~OVA88A{0`>vQK@85Q$I&R}>-@BQ41kLPF>P8L5;Qnq&k)E|RHb zv6RtEEg$ti*%>%O+~&-kxik0f8h79a7j=H;F^_ZR%oqsx&2N75o8R1T8yg$xfO0@B zpaXCNa2s$7zuC`f0A+yC|6p(s4ekL<0e%5~OM?Av1kepQ3P{*WhKB%cfO!Rj?eG@R z0!WKz!*>BX0gDC=x8gf4m6Uihz6$WcMZ>L#0#3)vsNYvqeg!ZMm;ekT*!OF|sv_6j zn9T+dTmYDpP;wQGtOpbV5(S(|fMP%cU<9xx!Ot9*=e6-AfX_CHu_bWwvaZ!FQ;F--#$a710D&UPk zI15if4`yH@;F`^zkm82m2ZX?NK%FE<3WiJ;IbH$2AlP?5;0$1=Bv)9FUEVcqe6_&i zbrSu97zGw2cJ+^pQ)V`y6DXe3ri@P$DBdpF|FMH)=b_A07A%LTk);46U4(!!yHR4( z88E4l@x1_Sm)H}ijj==5_zWWCu~3(-v2Q44dy>&%nFx((Iu5u@DAgp<_oTomw(<31 zZGf%!n}Dx^zdt4+uZ8H_qH~^)5^F12qW?=mp#fgO0}CdPh(6CHlcfdXD4pQM0TqNY zjS_wHo$MQU7I;b&NcKs{Z6@R%b>e_t4$)7UauptCct#wM=zB;b+-4F7sBR|?nBfo& zO7`vM^o=OUe$DBBRkH7hj_gg6ET^M@{Tl&87F<-2&okndWM69bg;X4HoNz$CM<-6+ z4Cs~YTTJL)rlLSShhW*G!%i-qFRb#-h6+JHFM)B<`2CpoQ@KNuBHysnP1JJ-V2SM$l>s`*HrV&LNmIY<#w$&TvO{+FQ7%bvHp?rDe(FC8c1)yQdRwu|BZE z;jL96IYB6wXUO;*BG$9w$u>>yLPGa)6_ULKNsS=~)Dip@2Ds~qR5)FgXx6PrfDa=6 zsp%Wy=0$)hCqy@i<8Yb*CGT;#pBUiXO{Bp+PRJ^WX&Mg1X~Qcyz4dR&%XxSH?{zmS z7gK4TzVnWw3Us_ec(g%NS$4a8%7P;_)C zT0MePH5h9L;8GnYbnG;-wnBo#N^2ZAphNILW0$E7=A7U?-`Q9>K#))|RGHji6Y#Q< zCumL82__tX#;+4P-FG9*u%+-$FcVrmsu{91-!2&WIb&0wBfQAw4sS#!I!GR&#{}}( z^PK{>N1%)djem#829wDWCOJ(uuTa@>y~L}mtITF5Y-WsMowEZjyGG)Y);jW~UZZDz5);AQRJ5lh5a)_*^F=uvHpvXq8x5 znss3G3_jAR@FKzT!XbOH+8oAvhd#W4>c{UcENHT;`K*B7{N^{m`OW?I2Z!ZLk&pUa Qng9R*07*qoM6N<$f-ZDgApigX literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_question.png b/app/src/main/res/drawable-xxhdpi/ic_question.png new file mode 100644 index 0000000000000000000000000000000000000000..cd7b78920cfad03cee0c7791a5a3ab71e381cb6f GIT binary patch literal 2444 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 Sh;?ZI0000 @@ -60,25 +60,128 @@ style="@style/setting_row_label" android:text="@string/nickname_label" /> + + + + + + + + + + + + + + + + + +