v0.1.0 プッシュ通知。タブの並び順が変わるバグの修正。

This commit is contained in:
tateisu 2017-04-25 15:54:05 +09:00
parent c4855394c4
commit a8c56f509f
29 changed files with 974 additions and 103 deletions

View File

@ -9,8 +9,8 @@ android {
applicationId "jp.juggler.subwaytooter"
minSdkVersion 21
targetSdkVersion 25
versionCode 8
versionName "0.0.8"
versionCode 10
versionName "0.1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {

View File

@ -3,6 +3,8 @@
package="jp.juggler.subwaytooter">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:name=".App1"
@ -12,6 +14,14 @@
android:supportsRtl="true"
android:theme="@style/AppTheme"
>
<receiver android:name=".AlarmReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<service android:name=".AlarmService" />
<activity
android:name=".ActMain"

View File

@ -13,6 +13,7 @@ import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.Switch;
import android.widget.TextView;
@ -29,10 +30,10 @@ public class ActAccountSetting extends AppCompatActivity implements View.OnClick
static final String KEY_ACCOUNT_DB_ID = "account_db_id";
public static void open( Activity activity, SavedAccount ai ,int requestCode){
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);
activity.startActivityForResult( intent, requestCode );
}
SavedAccount account;
@ -45,7 +46,7 @@ public class ActAccountSetting extends AppCompatActivity implements View.OnClick
if( account == null ) finish();
loadUIFromData( account );
btnOpenBrowser.setText(getString(R.string.open_instance_website,account.host));
btnOpenBrowser.setText( getString( R.string.open_instance_website, account.host ) );
}
TextView tvInstance;
@ -56,6 +57,10 @@ public class ActAccountSetting extends AppCompatActivity implements View.OnClick
Switch swConfirmBeforeBoost;
Switch swNSFWOpen;
Button btnOpenBrowser;
CheckBox cbNotificationMention;
CheckBox cbNotificationBoost;
CheckBox cbNotificationFavourite;
CheckBox cbNotificationFollow;
private void initUI(){
setContentView( R.layout.act_account_setting );
@ -67,6 +72,10 @@ public class ActAccountSetting extends AppCompatActivity implements View.OnClick
swConfirmBeforeBoost = (Switch) findViewById( R.id.swConfirmBeforeBoost );
swNSFWOpen = (Switch) findViewById( R.id.swNSFWOpen );
btnOpenBrowser = (Button) findViewById( R.id.btnOpenBrowser );
cbNotificationMention = (CheckBox) findViewById( R.id.cbNotificationMention );
cbNotificationBoost = (CheckBox) findViewById( R.id.cbNotificationBoost );
cbNotificationFavourite = (CheckBox) findViewById( R.id.cbNotificationFavourite );
cbNotificationFollow = (CheckBox) findViewById( R.id.cbNotificationFollow );
btnOpenBrowser.setOnClickListener( this );
btnAccessToken.setOnClickListener( this );
@ -75,8 +84,14 @@ public class ActAccountSetting extends AppCompatActivity implements View.OnClick
swNSFWOpen.setOnCheckedChangeListener( this );
swConfirmBeforeBoost.setOnCheckedChangeListener( this );
cbNotificationMention.setOnCheckedChangeListener( this );
cbNotificationBoost.setOnCheckedChangeListener( this );
cbNotificationFavourite.setOnCheckedChangeListener( this );
cbNotificationFollow.setOnCheckedChangeListener( this );
}
boolean loading = false;
private void loadUIFromData( SavedAccount a ){
tvInstance.setText( a.host );
tvUser.setText( a.acct );
@ -85,26 +100,44 @@ public class ActAccountSetting extends AppCompatActivity implements View.OnClick
if( sv != null ){
visibility = sv;
}
loading = true;
swConfirmBeforeBoost.setChecked( a.confirm_boost );
swNSFWOpen.setChecked( a.dont_hide_nsfw );
cbNotificationMention.setChecked( a.notification_mention );
cbNotificationBoost.setChecked( a.notification_boost );
cbNotificationFavourite.setChecked( a.notification_favourite );
cbNotificationFollow.setChecked( a.notification_follow );
loading = false;
updateVisibility();
}
@Override protected void onStop(){
AlarmService.startCheck(this);
super.onStop();
}
private void saveUIToData(){
account.visibility = visibility;
account.confirm_boost = swConfirmBeforeBoost.isChecked();
account.dont_hide_nsfw = swNSFWOpen.isChecked();
account.notification_mention = cbNotificationMention.isChecked();
account.notification_boost = cbNotificationBoost.isChecked();
account.notification_favourite = cbNotificationFavourite.isChecked();
account.notification_follow = cbNotificationFollow.isChecked();
account.saveSetting();
}
@Override
public void onCheckedChanged( CompoundButton buttonView, boolean isChecked ){
@Override public void onCheckedChanged( CompoundButton buttonView, boolean isChecked ){
if( loading ) return;
saveUIToData();
}
@Override
public void onClick( View v ){
@Override public void onClick( View v ){
switch( v.getId() ){
case R.id.btnAccessToken:
performAccessToken();
@ -116,10 +149,10 @@ public class ActAccountSetting extends AppCompatActivity implements View.OnClick
performVisibility();
break;
case R.id.btnOpenBrowser:
open_browser( "https://"+account.host+"/" );
open_browser( "https://" + account.host + "/" );
}
}
void open_browser( String url ){
try{
Intent intent = new Intent( Intent.ACTION_VIEW, Uri.parse( url ) );
@ -128,7 +161,7 @@ public class ActAccountSetting extends AppCompatActivity implements View.OnClick
ex.printStackTrace();
}
}
///////////////////////////////////////////////////
String visibility = TootStatus.VISIBILITY_PUBLIC;
@ -197,7 +230,7 @@ public class ActAccountSetting extends AppCompatActivity implements View.OnClick
///////////////////////////////////////////////////
private void performAccessToken(){
final ProgressDialog progress = new ProgressDialog( ActAccountSetting.this );
final AsyncTask< Void, String, TootApiResult > task = new AsyncTask< Void, String, TootApiResult >() {

View File

@ -47,8 +47,7 @@ public class ActAppSetting extends AppCompatActivity implements CompoundButton.O
}
private void saveUIToData(){
pref
.edit()
pref.edit()
.putBoolean( Pref.KEY_BACK_TO_COLUMN_LIST, swBackToColumnList.isChecked() )
.putBoolean( Pref.KEY_DONT_CONFIRM_BEFORE_CLOSE_COLUMN, swDontConfirmBeforeCloseColumn.isChecked() )
.apply();

View File

@ -80,29 +80,16 @@ public class ActMain extends AppCompatActivity
initUI();
Uri uri = ActOAuthCallback.last_uri.get();
if( uri != null ){
updateAccessToken( uri );
}
AlarmService.startCheck( this );
loadColumnList();
}
@Override protected void onNewIntent( Intent intent ){
super.onNewIntent( intent );
Uri uri = ActOAuthCallback.last_uri.get();
if( uri != null ){
updateAccessToken( uri );
}
}
@Override
protected void onDestroy(){
@Override protected void onDestroy(){
super.onDestroy();
}
@Override
protected void onResume(){
@Override protected void onResume(){
super.onResume();
HTMLDecoder.link_callback = link_click_listener;
@ -122,6 +109,7 @@ public class ActMain extends AppCompatActivity
}
if( bRemoved ){
pager_adapter.setOrder( pager, new_order );
saveColumnList();
}
}
@ -136,12 +124,16 @@ public class ActMain extends AppCompatActivity
if( pager_adapter.getCount() == 0 ){
llEmpty.setVisibility( View.VISIBLE );
}
Uri uri = ActOAuthCallback.last_uri.get();
if( uri != null ){
ActOAuthCallback.last_uri.set( null );
updateAccessToken( uri );
}
}
@Override
protected void onPause(){
@Override protected void onPause(){
HTMLDecoder.link_callback = null;
saveColumnList();
super.onPause();
}
@ -164,6 +156,7 @@ public class ActMain extends AppCompatActivity
ArrayList< Integer > order = data.getIntegerArrayListExtra( ActColumnList.EXTRA_ORDER );
if( order != null && isOrderChanged( order ) ){
pager_adapter.setOrder( pager, order );
saveColumnList();
}
if( pager_adapter.column_list.isEmpty() ){
@ -413,6 +406,25 @@ public class ActMain extends AppCompatActivity
private void updateAccessToken( final Uri uri ){
// 通知タップ
// subwaytooter://notification_click?db_id=(db_id)
String sv = uri.getQueryParameter( "db_id" );
if( ! TextUtils.isEmpty( sv ) ){
try{
long db_id = Long.parseLong( sv, 10 );
SavedAccount account = SavedAccount.loadAccount( log, db_id );
if( account != null ){
Column column = addColumn( account, Column.TYPE_NOTIFICATIONS );
if( ! column.bInitialLoading ){
column.reload();
}
}
}catch( Throwable ex ){
ex.printStackTrace();
}
return;
}
final ProgressDialog progress = new ProgressDialog( ActMain.this );
final AsyncTask< Void, Void, TootApiResult > task = new AsyncTask< Void, Void, TootApiResult >() {
@ -536,7 +548,8 @@ public class ActMain extends AppCompatActivity
this.row_id = SavedAccount.insert( host, user, result.object, result.token_info );
SavedAccount account = SavedAccount.loadAccount( log, row_id );
if( account != null ){
ActMain.this.onAccountUpdated( account );
AlarmService.startCheck( ActMain.this );
onAccountUpdated( account );
}
}
}
@ -580,6 +593,7 @@ public class ActMain extends AppCompatActivity
int page_showing = pager.getCurrentItem();
int page_delete = pager_adapter.column_list.indexOf( column );
pager_adapter.removeColumn( pager, column );
saveColumnList();
if( pager_adapter.getCount() == 0 ){
llEmpty.setVisibility( View.VISIBLE );
}else if( page_showing > 0 && page_showing == page_delete ){
@ -590,12 +604,12 @@ public class ActMain extends AppCompatActivity
//////////////////////////////////////////////////////////////
// カラム追加系
public void addColumn( SavedAccount ai, int type, Object... params ){
public Column addColumn( SavedAccount ai, int type, Object... params ){
// 既に同じカラムがあればそこに移動する
for( Column column : pager_adapter.column_list ){
if( column.isSameSpec( ai, type, params ) ){
pager.setCurrentItem( pager_adapter.column_list.indexOf( column ), true );
return;
return column;
}
}
//
@ -603,7 +617,9 @@ public class ActMain extends AppCompatActivity
//
Column col = new Column( ActMain.this, ai, type, params );
int idx = pager_adapter.addColumn( pager, col );
saveColumnList();
pager.setCurrentItem( idx, true );
return col;
}
private void onAccountUpdated( SavedAccount data ){
@ -641,7 +657,7 @@ public class ActMain extends AppCompatActivity
//////////////////////////////////////////////////////////////
public interface GetAccountCallback {
interface GetAccountCallback {
// return account information
// if failed, account is null.
void onGetAccount( TootAccount account );
@ -1100,7 +1116,7 @@ public class ActMain extends AppCompatActivity
////////////////////////////////////////
private void performAccountSetting(){
AccountPicker.pick( this, true,new AccountPicker.AccountPickerCallback() {
AccountPicker.pick( this, true, new AccountPicker.AccountPickerCallback() {
@Override
public void onAccountPicked( SavedAccount ai ){
ActAccountSetting.open( ActMain.this, ai, REQUEST_CODE_ACCOUNT_SETTING );
@ -1137,6 +1153,13 @@ public class ActMain extends AppCompatActivity
startActivityForResult( intent, REQUEST_CODE_COLUMN_LIST );
}
private void dumpColumnList(){
for( int i = 0, ie = pager_adapter.column_list.size() ; i < ie ; ++ i ){
Column column = pager_adapter.column_list.get( i );
log.d( "dumpColumnList [%s]%s %s", i, column.access_info.acct, column.getColumnName( true ) );
}
}
static final String FILE_COLUMN_LIST = "column_list";
private void saveColumnList(){
@ -1170,7 +1193,7 @@ public class ActMain extends AppCompatActivity
try{
JSONObject src = array.optJSONObject( i );
Column col = new Column( ActMain.this, src );
pager_adapter.addColumn( pager, col );
pager_adapter.addColumn( pager, col, pager_adapter.getCount() );
}catch( Throwable ex ){
ex.printStackTrace();
}
@ -1191,7 +1214,7 @@ public class ActMain extends AppCompatActivity
////////////////////////////////////////////////////////////////////////////
public interface RelationChangedCallback {
interface RelationChangedCallback {
// void onRelationChanged( TootRelationShip relationship );
void onRelationChanged();
}
@ -1226,8 +1249,8 @@ public class ActMain extends AppCompatActivity
if( result.object != null ){
remote_who = TootAccount.parse( log, access_info, result.object );
Utils.showToast( ActMain.this, false, bFollow ? R.string.follow_succeeded : R.string.unfollow_succeeded );
}else if( bFollow && who.locked && result.response.code() == 422 ){
Utils.showToast( ActMain.this, false, R.string.follow_succeeded );
}else if( who.locked && result.response.code() == 422 ){
Utils.showToast( ActMain.this, false, R.string.cant_follow_locked_user );
}else{
Utils.showToast( ActMain.this, false, result.error );
@ -1351,7 +1374,7 @@ public class ActMain extends AppCompatActivity
// アカウントを選択してからユーザをフォローする
void followFromAnotherAccount( final SavedAccount access_info, final TootAccount who, final RelationChangedCallback callback ){
AccountPicker.pick( ActMain.this, false,new AccountPicker.AccountPickerCallback() {
AccountPicker.pick( ActMain.this, false, new AccountPicker.AccountPickerCallback() {
@Override
public void onAccountPicked( SavedAccount ai ){
String acct = who.acct;
@ -1502,9 +1525,8 @@ public class ActMain extends AppCompatActivity
}.execute();
}
public interface ReportCompleteCallback {
interface ReportCompleteCallback {
void onReportComplete( TootApiResult result );
}
private void callReport( final SavedAccount account, final TootAccount who, final TootStatus status
@ -1605,7 +1627,7 @@ public class ActMain extends AppCompatActivity
if( ! tmp_list.isEmpty() ){
dialog.addAction( getString( R.string.favourite_from_another_account ), new Runnable() {
@Override public void run(){
AccountPicker.pick( ActMain.this, false,tmp_list, new AccountPicker.AccountPickerCallback() {
AccountPicker.pick( ActMain.this, false, tmp_list, new AccountPicker.AccountPickerCallback() {
@Override public void onAccountPicked( SavedAccount ai ){
if( ai != null ) performFavourite( ai, status );
}
@ -1614,7 +1636,7 @@ public class ActMain extends AppCompatActivity
} );
dialog.addAction( getString( R.string.boost_from_another_account ), new Runnable() {
@Override public void run(){
AccountPicker.pick( ActMain.this,false, tmp_list, new AccountPicker.AccountPickerCallback() {
AccountPicker.pick( ActMain.this, false, tmp_list, new AccountPicker.AccountPickerCallback() {
@Override public void onAccountPicked( SavedAccount ai ){
if( ai != null ) performBoost( ai, status, false );
}

View File

@ -0,0 +1,19 @@
package jp.juggler.subwaytooter;
import android.content.Context;
import android.content.Intent;
import android.support.v4.content.WakefulBroadcastReceiver;
public class AlarmReceiver extends WakefulBroadcastReceiver {
static final String EXTRA_RECEIVED_INTENT = "received_intent";
static final String ACTION_FROM_RECEIVER = "from_receiver";
@Override public void onReceive( Context context, Intent intent ){
Intent serviceIntent = new Intent(context,AlarmService.class);
serviceIntent.setAction( ACTION_FROM_RECEIVER );
serviceIntent.putExtra(EXTRA_RECEIVED_INTENT,intent);
startWakefulService(context,serviceIntent);
}
}

View File

@ -0,0 +1,493 @@
package jp.juggler.subwaytooter;
import android.app.AlarmManager;
import android.app.IntentService;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.PowerManager;
import android.os.SystemClock;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.WakefulBroadcastReceiver;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.concurrent.ConcurrentLinkedQueue;
import jp.juggler.subwaytooter.api.TootApiClient;
import jp.juggler.subwaytooter.api.TootApiResult;
import jp.juggler.subwaytooter.api.entity.TootNotification;
import jp.juggler.subwaytooter.table.NotificationTracking;
import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.util.Utils;
public class AlarmService extends IntentService {
static final LogCategory log = new LogCategory( "AlarmService" );
// PendingIntent request code
static final int PENDING_CODE_ALARM = 1;
static final String ACTION_NOTIFICATION_DELETE = "notification_delete";
static final String ACTION_NOTIFICATION_CLICK = "notification_click";
static final int NOTIFICATION_ID = 1;
static final long INTERVAL_MIN = 60000L * 5;
// Notifiation のJSONObject を日時でソートするためにデータを追加する
static final String KEY_TIME = "<>time";
private static final String ACTION_DATA_INJECTED = "data_injected";
private static final String EXTRA_DB_ID = "db_id";
public AlarmService(){
// name: Used to name the worker thread, important only for debugging.
super( "AlarmService" );
}
AlarmManager alarm_manager;
PowerManager power_manager;
NotificationManager notification_manager;
PowerManager.WakeLock wake_lock;
PendingIntent pi_next;
@Override public void onCreate(){
super.onCreate();
log.d("ctor");
alarm_manager = (AlarmManager) getApplicationContext().getSystemService( ALARM_SERVICE );
power_manager = (PowerManager) getApplicationContext().getSystemService( POWER_SERVICE );
notification_manager = (NotificationManager) getApplicationContext().getSystemService( NOTIFICATION_SERVICE );
wake_lock = power_manager.newWakeLock( PowerManager.PARTIAL_WAKE_LOCK, AlarmService.class.getName() );
wake_lock.setReferenceCounted( false );
wake_lock.acquire();
// 次回レシーバーを起こすためのPendingIntent
Intent next_intent = new Intent( this, AlarmReceiver.class );
pi_next = PendingIntent.getBroadcast( this, PENDING_CODE_ALARM, next_intent, PendingIntent.FLAG_UPDATE_CURRENT );
}
@Override public void onDestroy(){
log.d("dtor");
wake_lock.release();
super.onDestroy();
}
// IntentService onHandleIntent をワーカースレッドから呼び出す
// 同期処理を行って良い
@Override protected void onHandleIntent( @Nullable Intent intent ){
ArrayList< SavedAccount > account_list = SavedAccount.loadAccountList( log );
if( intent != null ){
String action = intent.getAction();
log.d("onHandleIntent action=%s",action);
if( ACTION_DATA_INJECTED.equals( action ) ){
processInjectedData();
}else if( AlarmReceiver.ACTION_FROM_RECEIVER.equals( action ) ){
WakefulBroadcastReceiver.completeWakefulIntent( intent );
//
Intent received_intent = intent.getParcelableExtra( AlarmReceiver.EXTRA_RECEIVED_INTENT );
if( received_intent != null ){
action = received_intent.getAction();
log.d("received_intent.action=%s",action);
if( Intent.ACTION_BOOT_COMPLETED.equals( action ) ){
NotificationTracking.resetPostAll();
}else if( ACTION_NOTIFICATION_DELETE.equals( action ) ){
log.d( "Notification deleted!" );
long db_id = received_intent.getLongExtra( EXTRA_DB_ID ,0L);
NotificationTracking.updateRead( db_id );
return;
}else if( ACTION_NOTIFICATION_CLICK.equals( action ) ){
log.d( "Notification clicked!" );
long db_id = received_intent.getLongExtra( EXTRA_DB_ID ,0L);
NotificationTracking.updateRead( db_id );
notification_manager.cancel( Long.toString(db_id),NOTIFICATION_ID );
//
intent = new Intent( this, ActOAuthCallback.class );
intent.setData( Uri.parse( "subwaytooter://notification_click?db_id="+ db_id ) );
intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK );
startActivity( intent );
return;
}
}
}
}
TootApiClient client = new TootApiClient( this, new TootApiClient.Callback() {
@Override public boolean isApiCancelled(){
return false;
}
@Override public void publishApiProgress( String s ){
}
} );
boolean bAlarmRequired = false;
if( account_list != null ){
for( SavedAccount account : account_list ){
try{
if( account.notification_mention
|| account.notification_boost
|| account.notification_favourite
|| account.notification_follow
){
bAlarmRequired = true;
ArrayList< Data > data_list = new ArrayList<>();
checkAccount( client, data_list, account );
showNotification( account.db_id, data_list );
}
}catch( Throwable ex ){
ex.printStackTrace();
}
}
}
alarm_manager.cancel( pi_next );
if( bAlarmRequired ){
long now = SystemClock.elapsedRealtime();
alarm_manager.setWindow(
AlarmManager.ELAPSED_REALTIME_WAKEUP
, now + INTERVAL_MIN
, 60000L * 10
, pi_next
);
log.d("alarm set!");
}else{
log.d("alarm is no longer required.");
}
}
private static class Data {
SavedAccount access_info;
TootNotification notification;
}
private static final String PATH_NOTIFICATIONS = "/api/v1/notifications";
private void checkAccount( TootApiClient client, ArrayList< Data > data_list, SavedAccount account ){
NotificationTracking nr = NotificationTracking.load( account.db_id );
// まずキャッシュされたデータを処理する
HashSet< Long > duplicate_check = new HashSet<>();
ArrayList< JSONObject > dst_array = new ArrayList<>();
if( nr.last_data != null ){
try{
JSONArray array = new JSONArray( nr.last_data );
for( int i = array.length() - 1 ; i >= 0 ; -- i ){
JSONObject src = array.optJSONObject( i );
update_sub( src, nr, account, dst_array, data_list, duplicate_check );
}
}catch( JSONException ex ){
ex.printStackTrace();
}
}
// 前回の更新から一定時刻が経過したら新しいデータを注ぎ足す
long now = System.currentTimeMillis();
if( now - nr.last_load >= INTERVAL_MIN ){
nr.last_load = now;
client.setAccount( account );
for( int nTry = 0 ; nTry < 4 ; ++ nTry ){
TootApiResult result = client.request( PATH_NOTIFICATIONS );
if( result == null ){
log.d( "cancelled." );
break;
}else if( result.array != null ){
try{
JSONArray array = result.array;
for( int i = array.length() - 1 ; i >= 0 ; -- i ){
JSONObject src = array.optJSONObject( i );
update_sub( src, nr, account, dst_array, data_list, duplicate_check );
}
}catch( JSONException ex ){
ex.printStackTrace();
}
break;
}else{
log.d( "error. %s", result.error );
}
}
}
Collections.sort( dst_array, new Comparator< JSONObject >() {
@Override public int compare( JSONObject a, JSONObject b ){
long la = a.optLong( KEY_TIME, 0 );
long lb = b.optLong( KEY_TIME, 0 );
// 新しい順
if( la < lb ) return + 1;
if( la > lb ) return - 1;
return 0;
}
} );
JSONArray d = new JSONArray();
for( int i = 0 ; i < 10 ; ++ i ){
if( i >= dst_array.size() ) break;
d.put( dst_array.get( i ) );
}
nr.last_data = d.toString();
nr.save();
}
void update_sub(
JSONObject src
, NotificationTracking nr
, SavedAccount account
, ArrayList< JSONObject > dst_array
, ArrayList< Data > data_list
, HashSet< Long > duplicate_check
) throws JSONException{
long id = src.optLong( "id" );
if( duplicate_check.contains( id ) ) return;
duplicate_check.add( id );
String type = Utils.optStringX( src, "type" );
if( id <= nr.nid_read ){
return;
}else if( id > nr.nid_show ){
// 種別チェックより先に表示済みidの更新を行う
nr.nid_show = id;
}
if( ( ! account.notification_mention && TootNotification.TYPE_MENTION.equals( type ) )
|| ( ! account.notification_boost && TootNotification.TYPE_REBLOG.equals( type ) )
|| ( ! account.notification_favourite && TootNotification.TYPE_FAVOURITE.equals( type ) )
|| ( ! account.notification_follow && TootNotification.TYPE_FOLLOW.equals( type ) )
){
return;
}
//
Data data = new Data();
data.access_info = account;
data.notification = TootNotification.parse( log, account, src );
if( data.notification != null ){
data_list.add( data );
//
src.put( KEY_TIME, data.notification.time_created_at );
dst_array.add( src );
}
}
public String getNotificationLine( String type, CharSequence display_name ){
if( TootNotification.TYPE_FAVOURITE.equals( type ) ){
return "- "+getString( R.string.display_name_favourited_by, display_name );
}
if( TootNotification.TYPE_REBLOG.equals( type ) ){
return "- "+getString( R.string.display_name_boosted_by, display_name );
}
if( TootNotification.TYPE_MENTION.equals( type ) ){
return "- "+getString( R.string.display_name_replied_by, display_name );
}
if( TootNotification.TYPE_FOLLOW.equals( type ) ){
return "- "+getString( R.string.display_name_followed_by, display_name );
}
return "- "+"?";
}
private void showNotification( long account_db_id,ArrayList< Data > data_list ){
String notification_tag = Long.toString( account_db_id );
if( data_list.isEmpty() ){
notification_manager.cancel( notification_tag,NOTIFICATION_ID );
return;
}
Collections.sort( data_list, new Comparator< Data >() {
@Override public int compare( Data a, Data b ){
long la = a.notification.time_created_at;
long lb = b.notification.time_created_at;
// 新しい順
if( la < lb ) return + 1;
if( la > lb ) return - 1;
return 0;
}
} );
Data item = data_list.get( 0 );
NotificationTracking nt = NotificationTracking.load( account_db_id );
if( item.notification.time_created_at == nt.post_time
&& item.notification.id == nt.post_id
){
// 先頭にあるデータが同じなら通知を更新しない
// このマーカーは端末再起動時にリセットされるので再起動後は通知が出るはず
return;
}
nt.updatePost( item.notification.id, item.notification.time_created_at );
// 通知タップ
Intent intent_click = new Intent( this, AlarmReceiver.class );
intent_click.putExtra(EXTRA_DB_ID,account_db_id);
intent_click.setAction( ACTION_NOTIFICATION_CLICK );
Intent intent_delete = new Intent( this, AlarmReceiver.class );
intent_click.putExtra(EXTRA_DB_ID,account_db_id);
intent_delete.setAction( ACTION_NOTIFICATION_DELETE );
PendingIntent pi_click = PendingIntent.getBroadcast( this, (256+(int)account_db_id), intent_click, PendingIntent.FLAG_UPDATE_CURRENT );
// 通知を消去した時のPendingIntent
PendingIntent pi_delete = PendingIntent.getBroadcast( this, (Integer.MAX_VALUE-(int)account_db_id), intent_delete, PendingIntent.FLAG_UPDATE_CURRENT );
NotificationCompat.Builder builder = new NotificationCompat.Builder( this )
.setContentIntent( pi_click )
.setDeleteIntent( pi_delete )
.setAutoCancel( false )
.setSmallIcon( R.drawable.ic_notification )
.setColor( ContextCompat.getColor( this, R.color.colorAccent ) )
.setDefaults( NotificationCompat.DEFAULT_ALL )
.setWhen( item.notification.time_created_at );
String a = getNotificationLine( item.notification.type, item.notification.account.display_name );
String acct = item.access_info.acct +" "+getString( R.string.app_name );
if( data_list.size() == 1 ){
builder.setContentTitle( a );
builder.setContentText( acct );
}else{
String header = getString( R.string.notification_count, data_list.size() );
builder.setContentTitle( header )
.setContentText( a );
NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle()
.setBigContentTitle( header )
.setSummaryText( acct );
for( int i = 0 ; i < 5 ; ++ i ){
if( i >= data_list.size() ) break;
item = data_list.get( i );
a = getNotificationLine( item.notification.type, item.notification.account.display_name );
style.addLine( a );
}
builder.setStyle( style );
}
notification_manager.notify( notification_tag,NOTIFICATION_ID, builder.build() );
}
////////////////////////////////////////////////////////////////////////////
// Activity との連携
public static void startCheck(Context context){
Intent intent = new Intent(context,AlarmReceiver.class);
context.sendBroadcast( intent );
}
private static class InjectData {
long account_db_id;
TootNotification.List list = new TootNotification.List();
}
static final ConcurrentLinkedQueue< InjectData > inject_queue = new ConcurrentLinkedQueue<>();
public static void injectData( Context context, long account_db_id, TootNotification.List src ){
InjectData data = new InjectData();
data.account_db_id = account_db_id;
data.list.addAll( src );
inject_queue.add( data );
Intent intent = new Intent( context, AlarmService.class );
intent.setAction( ACTION_DATA_INJECTED );
context.startService( intent );
}
private void processInjectedData(){
while( inject_queue.size() > 0 ){
InjectData data = inject_queue.poll();
SavedAccount account = SavedAccount.loadAccount( log, data.account_db_id );
if( account == null ) continue;
NotificationTracking nr = NotificationTracking.load( data.account_db_id );
HashSet< Long > duplicate_check = new HashSet<>();
ArrayList< JSONObject > dst_array = new ArrayList<>();
if( nr.last_data != null ){
// まずキャッシュされたデータを処理する
try{
JSONArray array = new JSONArray( nr.last_data );
for( int i = array.length() - 1 ; i >= 0 ; -- i ){
JSONObject src = array.optJSONObject( i );
dst_array.add( src );
duplicate_check.add( src.optLong( "id" ) );
}
}catch( JSONException ex ){
ex.printStackTrace();
}
}
for( TootNotification item : data.list ){
try{
if( duplicate_check.contains( item.id ) ) continue;
duplicate_check.add( item.id );
String type = item.type;
if( ( ! account.notification_mention && TootNotification.TYPE_MENTION.equals( type ) )
|| ( ! account.notification_boost && TootNotification.TYPE_REBLOG.equals( type ) )
|| ( ! account.notification_favourite && TootNotification.TYPE_FAVOURITE.equals( type ) )
|| ( ! account.notification_follow && TootNotification.TYPE_FOLLOW.equals( type ) )
){
continue;
}
//
JSONObject src = item.json;
src.put( KEY_TIME, item.time_created_at );
dst_array.add( src );
}catch( JSONException ex ){
ex.printStackTrace();
}
}
// 新しい順にソート
Collections.sort( dst_array, new Comparator< JSONObject >() {
@Override public int compare( JSONObject a, JSONObject b ){
long la = a.optLong( KEY_TIME, 0 );
long lb = b.optLong( KEY_TIME, 0 );
// 新しい順
if( la < lb ) return + 1;
if( la > lb ) return - 1;
return 0;
}
} );
// 最新10件を保存
JSONArray d = new JSONArray();
for( int i = 0 ; i < 10 ; ++ i ){
if( i >= dst_array.size() ) break;
d.put( dst_array.get( i ) );
}
nr.last_data = d.toString();
nr.save();
}
}
}

View File

@ -19,6 +19,7 @@ import jp.juggler.subwaytooter.table.ClientInfo;
import jp.juggler.subwaytooter.table.ContentWarning;
import jp.juggler.subwaytooter.table.LogData;
import jp.juggler.subwaytooter.table.MediaShown;
import jp.juggler.subwaytooter.table.NotificationTracking;
import jp.juggler.subwaytooter.table.SavedAccount;
import okhttp3.OkHttpClient;
import uk.co.chrisjenx.calligraphy.CalligraphyConfig;
@ -26,38 +27,11 @@ import uk.co.chrisjenx.calligraphy.TypefaceUtils;
public class App1 extends Application {
@Override
public void onCreate(){
super.onCreate();
CalligraphyConfig.initDefault(new CalligraphyConfig.Builder()
.setFontAttrId(R.attr.fontPath)
.build()
);
if( typeface_emoji == null ){
typeface_emoji = TypefaceUtils.load(getAssets(), "emojione_android.ttf");
}
if( db_open_helper == null ){
db_open_helper = new DBOpenHelper( getApplicationContext() );
}
if( image_loader == null ){
image_loader = new MyImageLoader(
Volley.newRequestQueue( getApplicationContext() )
, new BitmapCache()
);
}
}
@Override
public void onTerminate(){
super.onTerminate();
}
static final String DB_NAME = "app_db";
static final int DB_VERSION = 1;
static final int DB_VERSION = 2;
// 2017/4/25 v10 1=>2 SavedAccount に通知設定を追加
// 2017/4/25 v10 1=>2 NotificationTracking テーブルを追加
static DBOpenHelper db_open_helper;
@ -65,9 +39,10 @@ public class App1 extends Application {
return db_open_helper.getWritableDatabase();
}
static class DBOpenHelper extends SQLiteOpenHelper {
private static class DBOpenHelper extends SQLiteOpenHelper {
public DBOpenHelper( Context context ){
DBOpenHelper( Context context ){
super( context, DB_NAME, null, DB_VERSION );
}
@ -79,6 +54,7 @@ public class App1 extends Application {
ClientInfo.onDBCreate( db );
MediaShown.onDBCreate(db);
ContentWarning.onDBCreate(db);
NotificationTracking.onDBCreate(db);
}
@Override
@ -89,6 +65,7 @@ public class App1 extends Application {
ClientInfo.onDBUpgrade( db, oldVersion, newVersion );
MediaShown.onDBUpgrade( db, oldVersion, newVersion );
ContentWarning.onDBUpgrade( db, oldVersion, newVersion );
NotificationTracking.onDBUpgrade( db, oldVersion, newVersion );
}
}
@ -98,7 +75,7 @@ public class App1 extends Application {
return image_loader;
}
public static class MyImageLoader extends ImageLoader {
private static class MyImageLoader extends ImageLoader {
/**
* Constructs a new ImageLoader.
@ -122,7 +99,7 @@ public class App1 extends Application {
}
}
public static class BitmapCache implements ImageLoader.ImageCache {
private static class BitmapCache implements ImageLoader.ImageCache {
private LruCache<String, Bitmap> mCache;
@ -154,4 +131,38 @@ public class App1 extends Application {
// public static final RelationshipMap relationship_map = new RelationshipMap();
@Override
public void onCreate(){
super.onCreate();
CalligraphyConfig.initDefault(new CalligraphyConfig.Builder()
.setFontAttrId(R.attr.fontPath)
.build()
);
if( typeface_emoji == null ){
typeface_emoji = TypefaceUtils.load(getAssets(), "emojione_android.ttf");
}
if( db_open_helper == null ){
db_open_helper = new DBOpenHelper( getApplicationContext() );
if( BuildConfig.DEBUG){
// SQLiteDatabase db = db_open_helper.getWritableDatabase();
// db_open_helper.onCreate( db );
}
}
if( image_loader == null ){
image_loader = new MyImageLoader(
Volley.newRequestQueue( getApplicationContext() )
, new BitmapCache()
);
}
}
@Override
public void onTerminate(){
super.onTerminate();
}
}

View File

@ -1,5 +1,6 @@
package jp.juggler.subwaytooter;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
@ -72,9 +73,9 @@ class Column {
static final String KEY_COLUMN_NAME = "column_name";
static final String KEY_OLD_INDEX = "old_index";
private final ActMain activity;
private final @NonNull ActMain activity;
final SavedAccount access_info;
final @NonNull SavedAccount access_info;
final int type;
static final int TYPE_HOME = 1;
@ -82,7 +83,7 @@ class Column {
static final int TYPE_FEDERATE = 3;
static final int TYPE_PROFILE = 4;
static final int TYPE_FAVOURITES = 5;
static final int TYPE_REPORTS = 6;
private static final int TYPE_REPORTS = 6;
static final int TYPE_NOTIFICATIONS = 7;
static final int TYPE_CONVERSATION = 8;
static final int TYPE_HASHTAG = 9;
@ -106,7 +107,7 @@ class Column {
int scroll_pos;
int scroll_y;
Column( ActMain activity, @NonNull SavedAccount access_info, int type, Object... params ){
Column( @NonNull ActMain activity, @NonNull SavedAccount access_info, int type, Object... params ){
this.activity = activity;
this.access_info = access_info;
this.type = type;
@ -155,10 +156,13 @@ class Column {
item.put( KEY_OLD_INDEX, old_index );
}
Column( ActMain activity, JSONObject src ){
Column( @NonNull ActMain activity, JSONObject src ){
this.activity = activity;
this.access_info = SavedAccount.loadAccount( log, src.optLong( KEY_ACCOUNT_ROW_ID ) );
if( access_info == null ) throw new RuntimeException( "missing account" );
SavedAccount ac = SavedAccount.loadAccount( log, src.optLong( KEY_ACCOUNT_ROW_ID ) );
if( ac == null ) throw new RuntimeException( "missing account" );
this.access_info = ac;
this.type = src.optInt( KEY_TYPE );
switch( type ){
case TYPE_CONVERSATION:
@ -452,8 +456,14 @@ class Column {
TootApiResult parseNotifications( TootApiResult result ){
if( result != null ){
saveRange( result, true, true );
list_tmp = new ArrayList<>();
list_tmp.addAll( TootNotification.parseList( log, access_info, result.array ) );
TootNotification.List src= TootNotification.parseList( log, access_info, result.array );
if( src != null){
list_tmp = new ArrayList<>();
list_tmp.addAll( src );
//
AlarmService.injectData( activity,access_info.db_id, src );
}
}
return result;
}
@ -594,6 +604,8 @@ class Column {
if( list_tmp != null ){
list_data.clear();
list_data.addAll( list_tmp );
}
}
@ -700,8 +712,14 @@ class Column {
TootApiResult parseNotifications( TootApiResult result ){
if( result != null ){
saveRange( result, bBottom, ! bBottom );
list_tmp = new ArrayList<>();
list_tmp.addAll( TootNotification.parseList( log, access_info, result.array ) );
TootNotification.List src = TootNotification.parseList( log, access_info, result.array );
if( src != null ){
list_tmp = new ArrayList<>();
list_tmp.addAll( src );
//
AlarmService.injectData( activity,access_info.db_id, src );
}
}
return result;
}

View File

@ -6,7 +6,8 @@ import android.preference.PreferenceManager;
public class Pref {
public static SharedPreferences pref(Context context){
public static SharedPreferences pref( Context context){
return PreferenceManager.getDefaultSharedPreferences( context );
}

View File

@ -33,10 +33,13 @@ public class TootNotification extends TootId {
public long time_created_at;
public JSONObject json;
public static TootNotification parse( LogCategory log, LinkClickContext accopunt, JSONObject src ){
if( src == null ) return null;
try{
TootNotification dst = new TootNotification();
dst.json = src;
dst.id = src.optLong( "id" );
dst.type = Utils.optStringX( src, "type" );
dst.created_at = Utils.optStringX( src, "created_at" );

View File

@ -0,0 +1,154 @@
package jp.juggler.subwaytooter.table;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import jp.juggler.subwaytooter.App1;
import jp.juggler.subwaytooter.util.LogCategory;
public class NotificationTracking {
private static final LogCategory log = new LogCategory( "NotificationTracking" );
private static final String table = "noti_trac";
// アカウントDBの行ID サーバ側のIDではない
private static final String COL_ACCOUNT_DB_ID = "a";
// サーバから通知を取得した時刻
private static final String COL_LAST_LOAD = "ll";
// サーバから最後に読んだデータ既読は排除されてるかも
private static final String COL_LAST_DATA = "ld";
// 通知IDここまで既読
private static final String COL_NID_READ = "nr";
// 通知IDもっとも最近取得したもの
private static final String COL_NID_SHOW = "ns";
// 最後に表示した通知のID
private static final String COL_POST_ID = "pi";
// 最後に表示した通知の作成時刻
private static final String COL_POST_TIME = "pt";
public static void onDBCreate( SQLiteDatabase db ){
db.execSQL(
"create table if not exists " + table
+ "(_id INTEGER PRIMARY KEY"
+ ",a integer not null"
+ ",ll integer default 0"
+ ",ld text"
+ ",nr integer default 0"
+ ",ns integer default 0"
+ ",pi integer default 0"
+ ",pt integer default 0"
+ ")"
);
db.execSQL(
"create unique index if not exists " + table + "_a on " + table + "(a)"
);
}
public static void onDBUpgrade( SQLiteDatabase db, int oldVersion, int newVersion ){
if( oldVersion < 2 && newVersion >= 2 ){
onDBCreate( db );
}
}
private long account_db_id;
public long last_load;
public long nid_read;
public long nid_show;
public long post_id;
public long post_time;
public String last_data;
private static final String WHERE_AID = COL_ACCOUNT_DB_ID + "=?";
public static NotificationTracking load( long account_db_id ){
NotificationTracking dst = new NotificationTracking();
dst.account_db_id = account_db_id;
try{
Cursor cursor = App1.getDB().query( table, null,WHERE_AID, new String[]{ Long.toString( account_db_id ) }, null, null, null );
try{
if( cursor.moveToFirst() ){
dst.last_load = cursor.getLong( cursor.getColumnIndex( COL_LAST_LOAD ) );
dst.nid_read = cursor.getLong( cursor.getColumnIndex( COL_NID_READ ) );
dst.nid_show = cursor.getLong( cursor.getColumnIndex( COL_NID_SHOW ) );
dst.post_id = cursor.getLong( cursor.getColumnIndex( COL_POST_ID ) );
dst.post_time = cursor.getLong( cursor.getColumnIndex( COL_POST_TIME ) );
int idx_last_data = cursor.getColumnIndex( COL_LAST_DATA );
dst.last_data = cursor.isNull( idx_last_data ) ? null : cursor.getString( idx_last_data );
}
}finally{
cursor.close();
}
}catch( Throwable ex ){
log.e( ex, "load failed." );
}
return dst;
}
public void save(){
try{
ContentValues cv = new ContentValues();
cv.put( COL_ACCOUNT_DB_ID, account_db_id );
cv.put( COL_LAST_LOAD, last_load );
cv.put( COL_NID_READ, nid_read );
cv.put( COL_NID_SHOW, nid_show );
cv.put( COL_LAST_DATA, last_data );
App1.getDB().replace( table, null, cv );
}catch( Throwable ex ){
log.e( ex, "save failed." );
}
}
public void updatePost(long post_id,long post_time){
this.post_id = post_id;
this.post_time = post_time;
try{
ContentValues cv = new ContentValues();
cv.put( COL_POST_ID, post_id );
cv.put( COL_POST_TIME, post_time );
App1.getDB().update( table, cv,WHERE_AID, new String[]{ Long.toString( account_db_id ) } );
}catch( Throwable ex ){
log.e( ex, "save failed." );
}
}
public static void updateRead(long account_db_id){
try{
String[] where_args = new String[]{ Long.toString( account_db_id ) };
Cursor cursor = App1.getDB().query( table, new String[]{ COL_NID_SHOW }, WHERE_AID, where_args, null, null, null );
try{
if( cursor.moveToFirst() ){
long nid = cursor.getLong( cursor.getColumnIndex( COL_NID_SHOW ) );
ContentValues cv = new ContentValues();
cv.put( COL_NID_READ, nid );
App1.getDB().update( table, cv, WHERE_AID,where_args );
}
}finally{
cursor.close();
}
}catch( Throwable ex ){
log.e( ex, "load failed." );
}
}
public static void resetPostAll(){
try{
ContentValues cv = new ContentValues();
cv.put( COL_POST_ID, 0 );
cv.put( COL_POST_TIME, 0 );
App1.getDB().update( table, cv,null,null);
}catch( Throwable ex ){
log.e( ex, "save failed." );
}
}
}

View File

@ -29,6 +29,10 @@ public class SavedAccount extends TootAccount implements LinkClickContext{
private static final String COL_VISIBILITY = "visibility";
private static final String COL_CONFIRM_BOOST = "confirm_boost";
private static final String COL_DONT_HIDE_NSFW = "dont_hide_nsfw";
private static final String COL_NOTIFICATION_MENTION = "notification_mention";
private static final String COL_NOTIFICATION_BOOST = "notification_boost";
private static final String COL_NOTIFICATION_FAVOURITE = "notification_favourite";
private static final String COL_NOTIFICATION_FOLLOW = "notification_follow";
public static final long INVALID_ID = -1L;
@ -40,6 +44,10 @@ public class SavedAccount extends TootAccount implements LinkClickContext{
public String visibility;
public boolean confirm_boost;
public boolean dont_hide_nsfw;
public boolean notification_mention;
public boolean notification_boost;
public boolean notification_favourite;
public boolean notification_follow;
public static void onDBCreate( SQLiteDatabase db ){
db.execSQL(
@ -52,6 +60,11 @@ public class SavedAccount extends TootAccount implements LinkClickContext{
+ ",visibility text"
+ ",confirm_boost integer default 1"
+ ",dont_hide_nsfw integer default 0"
// 以下はDBスキーマ2で追加
+ ",notification_mention integer default 1"
+ ",notification_boost integer default 1"
+ ",notification_favourite integer default 1"
+ ",notification_follow integer default 1"
+ ")"
);
db.execSQL("create index if not exists " + table + "_user on " + table + "(u)" );
@ -59,11 +72,31 @@ public class SavedAccount extends TootAccount implements LinkClickContext{
}
public static void onDBUpgrade( SQLiteDatabase db, int oldVersion, int newVersion ){
if( oldVersion < 2 && newVersion >= 2){
try{
db.execSQL( "alter table "+table+" add column notification_mention integer default 1" );
}catch(Throwable ex){
ex.printStackTrace( );
}
try{
db.execSQL( "alter table "+table+" add column notification_boost integer default 1" );
}catch(Throwable ex){
ex.printStackTrace( );
}
try{
db.execSQL( "alter table "+table+" add column notification_favourite integer default 1" );
}catch(Throwable ex){
ex.printStackTrace( );
}
try{
db.execSQL( "alter table "+table+" add column notification_follow integer default 1" );
}catch(Throwable ex){
ex.printStackTrace( );
}
}
}
private SavedAccount(){
}
private static SavedAccount parse( Cursor cursor ) throws JSONException{
@ -81,6 +114,11 @@ public class SavedAccount extends TootAccount implements LinkClickContext{
dst.confirm_boost = ( 0 != cursor.getInt( cursor.getColumnIndex( COL_CONFIRM_BOOST ) ) );
dst.dont_hide_nsfw = ( 0 != cursor.getInt( cursor.getColumnIndex( COL_DONT_HIDE_NSFW ) ) );
dst.notification_mention = ( 0 != cursor.getInt( cursor.getColumnIndex( COL_NOTIFICATION_MENTION ) ) );
dst.notification_boost = ( 0 != cursor.getInt( cursor.getColumnIndex( COL_NOTIFICATION_BOOST ) ) );
dst.notification_favourite = ( 0 != cursor.getInt( cursor.getColumnIndex( COL_NOTIFICATION_FAVOURITE ) ) );
dst.notification_follow = ( 0 != cursor.getInt( cursor.getColumnIndex( COL_NOTIFICATION_FOLLOW ) ) );
dst.token_info = new JSONObject( cursor.getString( cursor.getColumnIndex( COL_TOKEN ) ) );
}
return dst;
@ -125,6 +163,12 @@ public class SavedAccount extends TootAccount implements LinkClickContext{
cv.put( COL_VISIBILITY, visibility );
cv.put( COL_CONFIRM_BOOST, confirm_boost? 1:0 );
cv.put( COL_DONT_HIDE_NSFW, dont_hide_nsfw ? 1: 0 );
cv.put( COL_NOTIFICATION_MENTION, notification_mention ? 1: 0 );
cv.put( COL_NOTIFICATION_BOOST, notification_boost ? 1: 0 );
cv.put( COL_NOTIFICATION_FAVOURITE, notification_favourite ? 1: 0 );
cv.put( COL_NOTIFICATION_FOLLOW, notification_follow ? 1: 0 );
App1.getDB().update( table, cv, COL_ID + "=?", new String[]{ Long.toString(db_id) } );
}
}
@ -138,6 +182,10 @@ public class SavedAccount extends TootAccount implements LinkClickContext{
this.confirm_boost = b.confirm_boost;
this.dont_hide_nsfw = b.dont_hide_nsfw;
this.token_info = b.token_info;
this.notification_mention = b.notification_follow;
this.notification_boost = b.notification_boost;
this.notification_favourite = b.notification_favourite;
this.notification_follow = b.notification_follow;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -3,6 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:fillViewport="true"
android:scrollbarStyle="outsideOverlay"
>
@ -61,7 +62,7 @@
android:id="@+id/btnOpenBrowser"
style="@style/setting_horizontal_stretch"
android:ellipsize="start"
android:text="@string/update_access_token"
tools:text="open http://mastodon.juggler.jp/"
android:textAllCaps="false"
/>
@ -118,8 +119,12 @@
<Switch
android:id="@+id/swConfirmBeforeBoost"
style="@style/setting_horizontal_stretch"
android:gravity="center"
style="@style/setting_wrap"
/>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"
/>
</LinearLayout>
@ -135,12 +140,56 @@
<Switch
android:id="@+id/swNSFWOpen"
style="@style/setting_horizontal_stretch"
android:gravity="center"
style="@style/setting_wrap"
/>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"
/>
</LinearLayout>
<View style="@style/setting_divider"/>
<TextView
style="@style/setting_row_label"
android:text="@string/notifications"
/>
<LinearLayout style="@style/setting_row_form">
<CheckBox
android:id="@+id/cbNotificationMention"
style="@style/setting_horizontal_stretch"
android:text="@string/mention2"
/>
</LinearLayout>
<LinearLayout style="@style/setting_row_form">
<CheckBox
android:id="@+id/cbNotificationBoost"
style="@style/setting_horizontal_stretch"
android:text="@string/boost"
/>
</LinearLayout>
<LinearLayout style="@style/setting_row_form">
<CheckBox
android:id="@+id/cbNotificationFavourite"
style="@style/setting_horizontal_stretch"
android:text="@string/favourite"
/>
</LinearLayout>
<LinearLayout style="@style/setting_row_form">
<CheckBox
android:id="@+id/cbNotificationFollow"
style="@style/setting_horizontal_stretch"
android:text="@string/follow"
/>
</LinearLayout>
<!--<View style="@style/setting_divider"/>-->
<!--<TextView-->

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -145,4 +145,8 @@
<string name="version_is">バージョン %1$s</string>
<string name="oss_license">OSSライセンス</string>
<string name="open_instance_website">https://%1$s/ を開く</string>
<string name="mention2">返信</string>
<string name="notification_count">%1$d件の通知</string>
<string name="boost">ブースト</string>
<string name="favourite">お気に入り</string>
</resources>

View File

@ -144,4 +144,8 @@
<string name="please_donate">Please support this app!</string>
<string name="version_is">version %1$s</string>
<string name="open_instance_website">open https://%1$s/</string>
<string name="boost">boost</string>
<string name="favourite">favourite</string>
<string name="mention2">mention</string>
<string name="notification_count">%1$d notifications</string>
</resources>

View File

@ -82,7 +82,10 @@
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_weight">1</item>
</style>
<style name="setting_wrap">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
</style>
<style name="setting_edit_text" parent="@style/setting_horizontal_stretch">
<item name="android:imeOptions">actionDone</item>
</style>

BIN
ic_notification-817.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -49,4 +49,4 @@ sub resize_scales{
my $res_dir = "app/src/main/res";
resize_scales( "ic_launcher-1024.png",$res_dir,"mipmap","ic_launcher",0,48);
#resize_scales( "ic_app_logo-512.png",$res_dir,"drawable","ic_app_logo",0,32);
#resize_scales( "ic_service-512.png",$res_dir,"drawable","ic_service",0,24);
resize_scales( "ic_notification-817.png",$res_dir,"drawable","ic_notification",0,24);