1156 lines
37 KiB
Java
1156 lines
37 KiB
Java
package jp.juggler.subwaytooter;
|
|
|
|
import android.Manifest;
|
|
import android.app.Activity;
|
|
import android.app.NotificationChannel;
|
|
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.provider.Settings;
|
|
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.ActionsDialog;
|
|
import jp.juggler.subwaytooter.table.AcctColor;
|
|
import jp.juggler.subwaytooter.table.SavedAccount;
|
|
import jp.juggler.subwaytooter.util.DecodeOptions;
|
|
import jp.juggler.subwaytooter.util.EmojiDecoder;
|
|
import jp.juggler.subwaytooter.util.LogCategory;
|
|
import jp.juggler.subwaytooter.util.NotificationHelper;
|
|
import jp.juggler.subwaytooter.util.Utils;
|
|
import jp.juggler.subwaytooter.view.MyNetworkImageView;
|
|
import okhttp3.Call;
|
|
import okhttp3.Request;
|
|
import okhttp3.RequestBody;
|
|
import okhttp3.Response;
|
|
|
|
public class ActAccountSetting extends AppCompatActivity
|
|
implements View.OnClickListener
|
|
, CompoundButton.OnCheckedChangeListener
|
|
{
|
|
|
|
static final LogCategory log = new LogCategory( "ActAccountSetting" );
|
|
|
|
static final String KEY_ACCOUNT_DB_ID = "account_db_id";
|
|
|
|
public static void open( Activity activity, SavedAccount ai, int requestCode ){
|
|
Intent intent = new Intent( activity, ActAccountSetting.class );
|
|
intent.putExtra( KEY_ACCOUNT_DB_ID, ai.db_id );
|
|
activity.startActivityForResult( intent, requestCode );
|
|
}
|
|
|
|
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 ) );
|
|
}
|
|
|
|
@Override protected void onStop(){
|
|
PollingWorker.queueUpdateNotification( this );
|
|
super.onStop();
|
|
}
|
|
|
|
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 ){
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
TextView tvInstance;
|
|
TextView tvUser;
|
|
View btnAccessToken;
|
|
View btnInputAccessToken;
|
|
View btnAccountRemove;
|
|
Button btnVisibility;
|
|
Switch swNSFWOpen;
|
|
Switch swDontShowTimeout;
|
|
Button btnOpenBrowser;
|
|
CheckBox cbNotificationMention;
|
|
CheckBox cbNotificationBoost;
|
|
CheckBox cbNotificationFavourite;
|
|
CheckBox cbNotificationFollow;
|
|
|
|
CheckBox cbConfirmFollow;
|
|
CheckBox cbConfirmFollowLockedUser;
|
|
CheckBox cbConfirmUnfollow;
|
|
CheckBox cbConfirmBoost;
|
|
CheckBox cbConfirmToot;
|
|
|
|
TextView tvUserCustom;
|
|
View btnUserCustom;
|
|
String full_acct;
|
|
|
|
Button btnNotificationSoundEdit;
|
|
Button btnNotificationSoundReset;
|
|
Button btnNotificationStyleEdit;
|
|
|
|
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 );
|
|
|
|
Styler.fixHorizontalPadding( findViewById( R.id.svContent ) );
|
|
|
|
tvInstance = findViewById( R.id.tvInstance );
|
|
tvUser = findViewById( R.id.tvUser );
|
|
btnAccessToken = findViewById( R.id.btnAccessToken );
|
|
btnInputAccessToken = findViewById( R.id.btnInputAccessToken );
|
|
btnAccountRemove = findViewById( R.id.btnAccountRemove );
|
|
btnVisibility = findViewById( R.id.btnVisibility );
|
|
swNSFWOpen = findViewById( R.id.swNSFWOpen );
|
|
swDontShowTimeout = findViewById( R.id.swDontShowTimeout );
|
|
btnOpenBrowser = findViewById( R.id.btnOpenBrowser );
|
|
cbNotificationMention = findViewById( R.id.cbNotificationMention );
|
|
cbNotificationBoost = findViewById( R.id.cbNotificationBoost );
|
|
cbNotificationFavourite = findViewById( R.id.cbNotificationFavourite );
|
|
cbNotificationFollow = findViewById( R.id.cbNotificationFollow );
|
|
|
|
cbConfirmFollow = findViewById( R.id.cbConfirmFollow );
|
|
cbConfirmFollowLockedUser = findViewById( R.id.cbConfirmFollowLockedUser );
|
|
cbConfirmUnfollow = findViewById( R.id.cbConfirmUnfollow );
|
|
cbConfirmBoost = findViewById( R.id.cbConfirmBoost );
|
|
cbConfirmToot = findViewById( R.id.cbConfirmToot );
|
|
|
|
tvUserCustom = findViewById( R.id.tvUserCustom );
|
|
btnUserCustom = findViewById( R.id.btnUserCustom );
|
|
|
|
ivProfileHeader = findViewById( R.id.ivProfileHeader );
|
|
ivProfileAvatar = findViewById( R.id.ivProfileAvatar );
|
|
btnProfileAvatar = findViewById( R.id.btnProfileAvatar );
|
|
btnProfileHeader = findViewById( R.id.btnProfileHeader );
|
|
etDisplayName = findViewById( R.id.etDisplayName );
|
|
btnDisplayName = findViewById( R.id.btnDisplayName );
|
|
etNote = 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 );
|
|
swDontShowTimeout.setOnCheckedChangeListener( this );
|
|
cbNotificationMention.setOnCheckedChangeListener( this );
|
|
cbNotificationBoost.setOnCheckedChangeListener( this );
|
|
cbNotificationFavourite.setOnCheckedChangeListener( this );
|
|
cbNotificationFollow.setOnCheckedChangeListener( this );
|
|
|
|
cbConfirmFollow.setOnCheckedChangeListener( this );
|
|
cbConfirmFollowLockedUser.setOnCheckedChangeListener( this );
|
|
cbConfirmUnfollow.setOnCheckedChangeListener( this );
|
|
cbConfirmBoost.setOnCheckedChangeListener( this );
|
|
cbConfirmToot.setOnCheckedChangeListener( this );
|
|
|
|
btnNotificationSoundEdit = findViewById( R.id.btnNotificationSoundEdit );
|
|
btnNotificationSoundReset = findViewById( R.id.btnNotificationSoundReset );
|
|
btnNotificationStyleEdit = findViewById( R.id.btnNotificationStyleEdit );
|
|
btnNotificationSoundEdit.setOnClickListener( this );
|
|
btnNotificationSoundReset.setOnClickListener( this );
|
|
btnNotificationStyleEdit.setOnClickListener( this );
|
|
}
|
|
|
|
boolean loading = false;
|
|
|
|
private void loadUIFromData( SavedAccount a ){
|
|
this.full_acct = a.acct;
|
|
|
|
tvInstance.setText( a.host );
|
|
tvUser.setText( a.acct );
|
|
|
|
String sv = a.visibility;
|
|
if( sv != null ){
|
|
visibility = sv;
|
|
}
|
|
|
|
loading = true;
|
|
|
|
swNSFWOpen.setChecked( a.dont_hide_nsfw );
|
|
swDontShowTimeout.setChecked( a.dont_show_timeout );
|
|
cbNotificationMention.setChecked( a.notification_mention );
|
|
cbNotificationBoost.setChecked( a.notification_boost );
|
|
cbNotificationFavourite.setChecked( a.notification_favourite );
|
|
cbNotificationFollow.setChecked( a.notification_follow );
|
|
|
|
cbConfirmFollow.setChecked( a.confirm_follow );
|
|
cbConfirmFollowLockedUser.setChecked( a.confirm_follow_locked );
|
|
cbConfirmUnfollow.setChecked( a.confirm_unfollow );
|
|
cbConfirmBoost.setChecked( a.confirm_boost );
|
|
cbConfirmToot.setChecked( a.confirm_post );
|
|
|
|
notification_sound_uri = a.sound_uri;
|
|
|
|
loading = false;
|
|
|
|
boolean enabled = ! a.isPseudo();
|
|
btnAccessToken.setEnabled( enabled );
|
|
btnInputAccessToken.setEnabled( enabled );
|
|
btnVisibility.setEnabled( enabled );
|
|
|
|
btnNotificationSoundEdit.setEnabled( Build.VERSION.SDK_INT < 26 && enabled );
|
|
btnNotificationSoundReset.setEnabled( Build.VERSION.SDK_INT < 26 && enabled );
|
|
btnNotificationStyleEdit.setEnabled( Build.VERSION.SDK_INT >= 26 && enabled );
|
|
|
|
cbNotificationMention.setEnabled( enabled );
|
|
cbNotificationBoost.setEnabled( enabled );
|
|
cbNotificationFavourite.setEnabled( enabled );
|
|
cbNotificationFollow.setEnabled( enabled );
|
|
|
|
cbConfirmFollow.setEnabled( enabled );
|
|
cbConfirmFollowLockedUser.setEnabled( enabled );
|
|
cbConfirmUnfollow.setEnabled( enabled );
|
|
cbConfirmBoost.setEnabled( enabled );
|
|
cbConfirmToot.setEnabled( enabled );
|
|
|
|
updateVisibility();
|
|
showAcctColor();
|
|
}
|
|
|
|
private void showAcctColor(){
|
|
AcctColor ac = AcctColor.load( full_acct );
|
|
tvUserCustom.setText( ac != null && ! TextUtils.isEmpty( ac.nickname ) ? ac.nickname : full_acct );
|
|
tvUserCustom.setTextColor( ac != null && ac.color_fg != 0 ? ac.color_fg : Styler.getAttributeColor( this, R.attr.colorTimeSmall ) );
|
|
tvUserCustom.setBackgroundColor( ac != null && ac.color_bg != 0 ? ac.color_bg : 0 );
|
|
}
|
|
|
|
private void saveUIToData(){
|
|
if( loading ) return;
|
|
account.visibility = visibility;
|
|
account.dont_hide_nsfw = swNSFWOpen.isChecked();
|
|
account.dont_show_timeout = swDontShowTimeout.isChecked();
|
|
account.notification_mention = cbNotificationMention.isChecked();
|
|
account.notification_boost = cbNotificationBoost.isChecked();
|
|
account.notification_favourite = cbNotificationFavourite.isChecked();
|
|
account.notification_follow = cbNotificationFollow.isChecked();
|
|
|
|
account.sound_uri = notification_sound_uri == null ? "" : notification_sound_uri;
|
|
|
|
account.confirm_follow = cbConfirmFollow.isChecked();
|
|
account.confirm_follow_locked = cbConfirmFollowLockedUser.isChecked();
|
|
account.confirm_unfollow = cbConfirmUnfollow.isChecked();
|
|
account.confirm_boost = cbConfirmBoost.isChecked();
|
|
account.confirm_post = cbConfirmToot.isChecked();
|
|
|
|
account.saveSetting();
|
|
|
|
}
|
|
|
|
@Override public void onCheckedChanged( CompoundButton buttonView, boolean isChecked ){
|
|
saveUIToData();
|
|
}
|
|
|
|
@Override public void onClick( View v ){
|
|
switch( v.getId() ){
|
|
case R.id.btnAccessToken:
|
|
performAccessToken();
|
|
break;
|
|
case R.id.btnInputAccessToken:
|
|
inputAccessToken();
|
|
break;
|
|
|
|
case R.id.btnAccountRemove:
|
|
performAccountRemove();
|
|
break;
|
|
case R.id.btnVisibility:
|
|
performVisibility();
|
|
break;
|
|
case R.id.btnOpenBrowser:
|
|
open_browser( "https://" + account.host + "/" );
|
|
break;
|
|
|
|
case R.id.btnUserCustom:
|
|
ActNickname.open( this, full_acct, false, REQUEST_CODE_ACCT_CUSTOMIZE );
|
|
break;
|
|
|
|
case R.id.btnNotificationSoundEdit:
|
|
openNotificationSoundPicker();
|
|
break;
|
|
|
|
case R.id.btnNotificationSoundReset:
|
|
notification_sound_uri = "";
|
|
saveUIToData();
|
|
break;
|
|
|
|
case R.id.btnProfileAvatar:
|
|
pickAvatarImage();
|
|
break;
|
|
|
|
case R.id.btnProfileHeader:
|
|
pickHeaderImage();
|
|
break;
|
|
|
|
case R.id.btnDisplayName:
|
|
sendDisplayName(false);
|
|
break;
|
|
|
|
case R.id.btnNote:
|
|
sendNote(false);
|
|
break;
|
|
|
|
case R.id.btnNotificationStyleEdit:
|
|
if( Build.VERSION.SDK_INT >= 26 ){
|
|
NotificationChannel channel = NotificationHelper.createNotificationChannel( this,account );
|
|
Intent intent = new Intent( Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS );
|
|
intent.putExtra( Settings.EXTRA_CHANNEL_ID, channel.getId() );
|
|
intent.putExtra( Settings.EXTRA_APP_PACKAGE, getPackageName() );
|
|
startActivity( intent );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void open_browser( String url ){
|
|
try{
|
|
Intent intent = new Intent( Intent.ACTION_VIEW, Uri.parse( url ) );
|
|
startActivity( intent );
|
|
}catch( Throwable ex ){
|
|
log.trace( ex );
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////
|
|
|
|
String visibility = TootStatus.VISIBILITY_PUBLIC;
|
|
|
|
private void updateVisibility(){
|
|
btnVisibility.setText( Styler.getVisibilityString( this, visibility ) );
|
|
}
|
|
|
|
private void performVisibility(){
|
|
final CharSequence[] caption_list = new CharSequence[]{
|
|
Styler.getVisibilityCaption( this, TootStatus.VISIBILITY_WEB_SETTING ),
|
|
Styler.getVisibilityCaption( this, TootStatus.VISIBILITY_PUBLIC ),
|
|
Styler.getVisibilityCaption( this, TootStatus.VISIBILITY_UNLISTED ),
|
|
Styler.getVisibilityCaption( this, TootStatus.VISIBILITY_PRIVATE),
|
|
Styler.getVisibilityCaption( this, TootStatus.VISIBILITY_DIRECT),
|
|
};
|
|
|
|
new AlertDialog.Builder( this )
|
|
.setTitle( R.string.choose_visibility )
|
|
.setItems( caption_list, new DialogInterface.OnClickListener() {
|
|
@Override
|
|
public void onClick( DialogInterface dialog, int which ){
|
|
switch( which ){
|
|
case 0:
|
|
visibility = TootStatus.VISIBILITY_WEB_SETTING;
|
|
break;
|
|
case 1:
|
|
visibility = TootStatus.VISIBILITY_PUBLIC;
|
|
break;
|
|
case 2:
|
|
visibility = TootStatus.VISIBILITY_UNLISTED;
|
|
break;
|
|
case 3:
|
|
visibility = TootStatus.VISIBILITY_PRIVATE;
|
|
break;
|
|
case 4:
|
|
visibility = TootStatus.VISIBILITY_DIRECT;
|
|
break;
|
|
}
|
|
updateVisibility();
|
|
saveUIToData();
|
|
}
|
|
} )
|
|
.setNegativeButton( R.string.cancel, null )
|
|
.show();
|
|
|
|
}
|
|
|
|
///////////////////////////////////////////////////
|
|
private void performAccountRemove(){
|
|
new AlertDialog.Builder( this )
|
|
.setTitle( R.string.confirm )
|
|
.setMessage( R.string.confirm_account_remove )
|
|
.setNegativeButton( R.string.cancel, null )
|
|
.setPositiveButton( R.string.ok, new DialogInterface.OnClickListener() {
|
|
@Override public void onClick( DialogInterface dialog, int which ){
|
|
account.delete();
|
|
|
|
SharedPreferences pref = Pref.pref( ActAccountSetting.this );
|
|
if( account.db_id == pref.getLong( Pref.KEY_TABLET_TOOT_DEFAULT_ACCOUNT, - 1L ) ){
|
|
pref.edit().putLong( Pref.KEY_TABLET_TOOT_DEFAULT_ACCOUNT, - 1L ).apply();
|
|
}
|
|
|
|
finish();
|
|
|
|
new AsyncTask< Void, Void, String >() {
|
|
|
|
void unregister(){
|
|
try{
|
|
|
|
String install_id = PrefDevice.prefDevice( ActAccountSetting.this ).getString( PrefDevice.KEY_INSTALL_ID, null );
|
|
if( TextUtils.isEmpty( install_id ) ){
|
|
log.d( "performAccountRemove: missing install_id" );
|
|
return;
|
|
}
|
|
|
|
String tag = account.notification_tag;
|
|
if( TextUtils.isEmpty( tag ) ){
|
|
log.d( "performAccountRemove: missing notification_tag" );
|
|
return;
|
|
}
|
|
|
|
String post_data = "instance_url=" + Uri.encode( "https://" + account.host )
|
|
+ "&app_id=" + Uri.encode( getPackageName() )
|
|
+ "&tag=" + tag;
|
|
|
|
Request request = new Request.Builder()
|
|
.url( PollingWorker.APP_SERVER + "/unregister" )
|
|
.post( RequestBody.create( TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, post_data ) )
|
|
.build();
|
|
|
|
Call call = App1.ok_http_client.newCall( request );
|
|
|
|
Response response = call.execute();
|
|
|
|
log.e( "performAccountRemove: %s", response );
|
|
|
|
}catch( Throwable ex ){
|
|
log.trace( ex );
|
|
}
|
|
}
|
|
|
|
@Override protected String doInBackground( Void... params ){
|
|
unregister();
|
|
return null;
|
|
}
|
|
|
|
@Override protected void onCancelled( String s ){
|
|
onPostExecute( s );
|
|
}
|
|
|
|
@Override protected void onPostExecute( String s ){
|
|
}
|
|
}.executeOnExecutor( App1.task_executor );
|
|
}
|
|
} )
|
|
.show();
|
|
|
|
}
|
|
|
|
///////////////////////////////////////////////////
|
|
private void performAccessToken(){
|
|
|
|
//noinspection deprecation
|
|
final ProgressDialog progress = new ProgressDialog( ActAccountSetting.this );
|
|
|
|
final AsyncTask< Void, String, TootApiResult > task = new AsyncTask< Void, String, TootApiResult >() {
|
|
|
|
@Override
|
|
protected TootApiResult doInBackground( Void... params ){
|
|
TootApiClient api_client = new TootApiClient( ActAccountSetting.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 );
|
|
return api_client.authorize1( Pref.pref( ActAccountSetting.this ).getString( Pref.KEY_CLIENT_NAME, "" ) );
|
|
}
|
|
|
|
@Override protected void onPostExecute( TootApiResult result ){
|
|
progress.dismiss();
|
|
|
|
if( result == null ){
|
|
// cancelled.
|
|
}else if( result.error != null ){
|
|
// エラー?
|
|
String sv = result.error;
|
|
if( sv.startsWith( "https" ) ){
|
|
// OAuth認証が必要
|
|
Intent data = new Intent();
|
|
data.setData( Uri.parse( sv ) );
|
|
setResult( RESULT_OK, data );
|
|
finish();
|
|
return;
|
|
}
|
|
// 他のエラー
|
|
Utils.showToast( ActAccountSetting.this, true, sv );
|
|
log.e( result.error );
|
|
}else{
|
|
// 多分ここは通らないはず
|
|
Utils.showToast( ActAccountSetting.this, false, R.string.access_token_updated_for );
|
|
}
|
|
}
|
|
};
|
|
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 );
|
|
}
|
|
|
|
static final int RESULT_INPUT_ACCESS_TOKEN = RESULT_FIRST_USER + 10;
|
|
static final String EXTRA_DB_ID = "db_id";
|
|
|
|
private void inputAccessToken(){
|
|
|
|
Intent data = new Intent();
|
|
data.putExtra( EXTRA_DB_ID, account.db_id );
|
|
setResult( RESULT_INPUT_ACCESS_TOKEN, data );
|
|
finish();
|
|
}
|
|
|
|
private void openNotificationSoundPicker(){
|
|
Intent intent = new Intent( RingtoneManager.ACTION_RINGTONE_PICKER );
|
|
intent.putExtra( RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION );
|
|
intent.putExtra( RingtoneManager.EXTRA_RINGTONE_TITLE, R.string.notification_sound );
|
|
intent.putExtra( RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false );
|
|
intent.putExtra( RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false );
|
|
try{
|
|
Uri uri = TextUtils.isEmpty( notification_sound_uri ) ? null : Uri.parse( notification_sound_uri );
|
|
if( uri != null ){
|
|
intent.putExtra( RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, uri );
|
|
}
|
|
}catch( Throwable ignored ){
|
|
}
|
|
|
|
Intent chooser = Intent.createChooser( intent, getString( R.string.notification_sound ) );
|
|
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(){
|
|
// サーバから情報をロードする
|
|
|
|
//noinspection deprecation
|
|
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( EmojiDecoder.decodeEmoji( this, src.display_name == null ? "" : src.display_name ,null) );
|
|
|
|
if( src.source != null && src.source.note != null ){
|
|
etNote.setText( EmojiDecoder.decodeEmoji( this, src.source.note ,null) );
|
|
}else if( src.note != null ){
|
|
|
|
etNote.setText( new DecodeOptions().decodeHTML(ActAccountSetting.this, account, src.note) );
|
|
}else{
|
|
etNote.setText( "" );
|
|
}
|
|
|
|
// 編集可能にする
|
|
btnProfileAvatar.setEnabled( true );
|
|
btnProfileHeader.setEnabled( true );
|
|
etDisplayName.setEnabled( true );
|
|
btnDisplayName.setEnabled( true );
|
|
etNote.setEnabled( true );
|
|
btnNote.setEnabled( true );
|
|
}
|
|
|
|
void updateCredential( final String form_data ){
|
|
|
|
//noinspection deprecation
|
|
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 ){
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
static final int max_length_display_name = 30;
|
|
static final int max_length_note = 160;
|
|
|
|
private void sendDisplayName(boolean bConfirmed){
|
|
String sv = etDisplayName.getText().toString();
|
|
if( !bConfirmed ){
|
|
int length = sv.codePointCount( 0,sv.length() );
|
|
if( length > max_length_display_name ){
|
|
new AlertDialog.Builder( this )
|
|
.setMessage( getString(R.string.length_warning
|
|
,getString(R.string.display_name)
|
|
,length,max_length_display_name
|
|
))
|
|
.setNegativeButton( R.string.cancel ,null)
|
|
.setPositiveButton( R.string.ok, new DialogInterface.OnClickListener() {
|
|
@Override public void onClick( DialogInterface dialogInterface, int i ){
|
|
sendDisplayName(true);
|
|
}
|
|
} )
|
|
.setCancelable( true )
|
|
.show();
|
|
return;
|
|
}
|
|
}
|
|
updateCredential( "display_name=" + Uri.encode(EmojiDecoder.decodeShortCode(sv) ) );
|
|
}
|
|
|
|
private void sendNote(boolean bConfirmed){
|
|
String sv = etNote.getText().toString();
|
|
if( !bConfirmed ){
|
|
int length = sv.codePointCount( 0,sv.length() );
|
|
if( length > max_length_note ){
|
|
new AlertDialog.Builder( this )
|
|
.setMessage( getString(R.string.length_warning
|
|
,getString(R.string.note)
|
|
,length,max_length_note
|
|
))
|
|
.setNegativeButton( R.string.cancel ,null)
|
|
.setPositiveButton( R.string.ok, new DialogInterface.OnClickListener() {
|
|
@Override public void onClick( DialogInterface dialogInterface, int i ){
|
|
sendNote(true);
|
|
}
|
|
} )
|
|
.setCancelable( true )
|
|
.show();
|
|
return;
|
|
}
|
|
}
|
|
updateCredential( "note=" + Uri.encode(EmojiDecoder.decodeShortCode(sv) ) );
|
|
}
|
|
|
|
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_permission_to_access_media );
|
|
}
|
|
|
|
@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_permission_to_access_media );
|
|
}
|
|
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 );
|
|
}
|
|
|
|
}
|
|
|